1+ /*
2+ ==============================================================================
3+ This file is part of the YUP library.
4+ Copyright (c) 2025 - [email protected] 5+ YUP is an open source library subject to open-source licensing.
6+ The code included in this file is provided under the terms of the ISC license
7+ http://www.isc.org/downloads/software-support-policy/isc-license. Permission
8+ to use, copy, modify, and/or distribute this software for any purpose with or
9+ without fee is hereby granted provided that the above copyright notice and
10+ this permission notice appear in all copies.
11+ YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
12+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
13+ DISCLAIMED.
14+ ==============================================================================
15+ */
16+
17+ namespace yup
18+ {
19+
20+ namespace
21+ {
22+
23+ // ==============================================================================
24+
25+ class Mp3AudioFormatReader : public AudioFormatReader
26+ {
27+ public:
28+ Mp3AudioFormatReader (InputStream* sourceStream);
29+ ~Mp3AudioFormatReader () override ;
30+
31+ bool readSamples (float * const * destChannels,
32+ int numDestChannels,
33+ int startOffsetInDestBuffer,
34+ int64 startSampleInFile,
35+ int numSamples) override ;
36+
37+ private:
38+ static size_t readCallback (void * pUserData, void * pBufferOut, size_t bytesToRead)
39+ {
40+ auto * stream = static_cast <InputStream*> (pUserData);
41+ return (size_t ) stream->read (pBufferOut, (int ) bytesToRead);
42+ }
43+
44+ static drmp3_bool32 seekCallback (void * pUserData, int offset, drmp3_seek_origin origin)
45+ {
46+ auto * stream = static_cast <InputStream*> (pUserData);
47+
48+ if (origin == DRMP3_SEEK_SET)
49+ return stream->setPosition (offset) ? DRMP3_TRUE : DRMP3_FALSE;
50+ else if (origin == DRMP3_SEEK_CUR)
51+ return stream->setPosition (stream->getPosition () + offset) ? DRMP3_TRUE : DRMP3_FALSE;
52+
53+ return DRMP3_FALSE;
54+ }
55+
56+ static drmp3_bool32 tellCallback (void * pUserData, drmp3_int64* pCursor)
57+ {
58+ auto * stream = static_cast <InputStream*> (pUserData);
59+ *pCursor = stream->getPosition ();
60+ return DRMP3_TRUE;
61+ }
62+
63+ static void metaCallback (void * pUserData, const drmp3_metadata* pMetadata)
64+ {
65+ auto * reader = static_cast <Mp3AudioFormatReader*> (pUserData);
66+ if (reader && pMetadata && pMetadata->pRawData )
67+ {
68+ // Handle metadata based on type
69+ switch (pMetadata->type )
70+ {
71+ case DRMP3_METADATA_TYPE_ID3V1:
72+ case DRMP3_METADATA_TYPE_ID3V2:
73+ case DRMP3_METADATA_TYPE_APE:
74+ {
75+ // For now, we'll just store the raw metadata. In a real implementation,
76+ // you would parse the metadata and extract useful information like
77+ // title, artist, album, etc.
78+ break ;
79+ }
80+ case DRMP3_METADATA_TYPE_XING:
81+ case DRMP3_METADATA_TYPE_VBRI:
82+ {
83+ // Xing/VBRI headers contain VBR information and seek tables
84+ break ;
85+ }
86+ default :
87+ break ;
88+ }
89+ }
90+ }
91+
92+ drmp3 mp3 = {};
93+ HeapBlock<float > tempBuffer;
94+ size_t tempBufferSize = 0 ;
95+ bool isOpen = false ;
96+ int64 currentPCMFrame = 0 ;
97+
98+ YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Mp3AudioFormatReader)
99+ };
100+
101+ Mp3AudioFormatReader::Mp3AudioFormatReader (InputStream* sourceStream)
102+ : AudioFormatReader (sourceStream, " MP3 file" )
103+ {
104+ if (sourceStream == nullptr )
105+ return ;
106+
107+ isOpen = drmp3_init (&mp3, readCallback, seekCallback, tellCallback, metaCallback, sourceStream, nullptr ) == DRMP3_TRUE;
108+
109+ if (isOpen)
110+ {
111+ sampleRate = mp3.sampleRate ;
112+ bitsPerSample = 16 ; // MP3 always outputs 16-bit samples
113+ lengthInSamples = mp3.totalPCMFrameCount ;
114+ numChannels = mp3.channels ;
115+ usesFloatingPointData = false ; // MP3 outputs 16-bit integer samples
116+
117+ // Allocate temp buffer for reading
118+ const auto bytesPerFrame = numChannels * (bitsPerSample / 8 );
119+ tempBufferSize = bytesPerFrame * 4096 ;
120+ tempBuffer.allocate (tempBufferSize / sizeof (float ), true );
121+ }
122+ }
123+
124+ Mp3AudioFormatReader::~Mp3AudioFormatReader ()
125+ {
126+ if (isOpen)
127+ drmp3_uninit (&mp3);
128+ }
129+
130+ bool Mp3AudioFormatReader::readSamples (float * const * destChannels,
131+ int numDestChannels,
132+ int startOffsetInDestBuffer,
133+ int64 startSampleInFile,
134+ int numSamples)
135+ {
136+ if (! isOpen)
137+ return false ;
138+
139+ if (numSamples <= 0 )
140+ return true ;
141+
142+ // Seek to the start position if needed
143+ if (startSampleInFile != currentPCMFrame)
144+ {
145+ if (! drmp3_seek_to_pcm_frame (&mp3, startSampleInFile))
146+ return false ;
147+ currentPCMFrame = startSampleInFile;
148+ }
149+
150+ const auto numChannelsToRead = jmin (numDestChannels, numChannels);
151+ const auto bytesPerSample = bitsPerSample / 8 ;
152+ const auto bytesPerFrame = numChannels * bytesPerSample;
153+
154+ // Create output channel pointers offset by the start position
155+ HeapBlock<float *> offsetDestChannels;
156+ offsetDestChannels.malloc (numDestChannels);
157+
158+ for (int ch = 0 ; ch < numDestChannels; ++ch)
159+ {
160+ offsetDestChannels[ch] = destChannels[ch] + startOffsetInDestBuffer;
161+ }
162+
163+ drmp3_uint64 framesRead = 0 ;
164+ int samplesToRead = numSamples;
165+
166+ while (samplesToRead > 0 )
167+ {
168+ const auto framesToRead = jmin (samplesToRead, (int ) (tempBufferSize / (numChannels * sizeof (float ))));
169+
170+ if (framesToRead <= 0 )
171+ break ;
172+
173+ // Read MP3 frames into temp buffer
174+ auto framesJustRead = drmp3_read_pcm_frames_f32 (&mp3, framesToRead, tempBuffer.getData ());
175+
176+ if (framesJustRead == 0 )
177+ break ;
178+
179+ // Convert and deinterleave the samples
180+ using SourceFormat = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>;
181+ using DestFormat = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>;
182+
183+ AudioData::deinterleaveSamples (AudioData::InterleavedSource<SourceFormat> { tempBuffer.getData (), numChannels },
184+ AudioData::NonInterleavedDest<DestFormat> { offsetDestChannels.getData (), numChannelsToRead },
185+ (int ) framesJustRead);
186+
187+ // Fill remaining channels with copies if requested
188+ for (int ch = numChannelsToRead; ch < numDestChannels; ++ch)
189+ {
190+ if (offsetDestChannels[ch] != nullptr )
191+ zeromem (offsetDestChannels[ch], sizeof (float ) * framesJustRead);
192+ }
193+
194+ // Update pointers and counters
195+ for (int ch = 0 ; ch < numDestChannels; ++ch)
196+ {
197+ if (offsetDestChannels[ch] != nullptr )
198+ offsetDestChannels[ch] += framesJustRead;
199+ }
200+
201+ framesRead += framesJustRead;
202+ samplesToRead -= (int ) framesJustRead;
203+ currentPCMFrame += framesJustRead;
204+ }
205+
206+ return framesRead > 0 ;
207+ }
208+
209+ } // namespace
210+
211+ // ==============================================================================
212+ // Mp3AudioFormat implementation
213+ Mp3AudioFormat::Mp3AudioFormat ()
214+ : formatName (" MP3 file" )
215+ {
216+ }
217+
218+ Mp3AudioFormat::~Mp3AudioFormat () = default ;
219+
220+ const String& Mp3AudioFormat::getFormatName () const
221+ {
222+ return formatName;
223+ }
224+
225+ Array<String> Mp3AudioFormat::getFileExtensions () const
226+ {
227+ return { " .mp3" };
228+ }
229+
230+ std::unique_ptr<AudioFormatReader> Mp3AudioFormat::createReaderFor (InputStream* sourceStream)
231+ {
232+ auto reader = std::make_unique<Mp3AudioFormatReader> (sourceStream);
233+
234+ if (reader->sampleRate > 0 && reader->numChannels > 0 )
235+ return reader;
236+
237+ return nullptr ;
238+ }
239+
240+ std::unique_ptr<AudioFormatWriter> Mp3AudioFormat::createWriterFor (OutputStream* streamToWriteTo,
241+ double sampleRate,
242+ int numberOfChannels,
243+ int bitsPerSample,
244+ const StringPairArray& metadataValues,
245+ int qualityOptionIndex)
246+ {
247+ // MP3 encoding is not implemented in this version
248+ return nullptr ;
249+ }
250+
251+ Array<int > Mp3AudioFormat::getPossibleBitDepths () const
252+ {
253+ return { 16 };
254+ }
255+
256+ Array<int > Mp3AudioFormat::getPossibleSampleRates () const
257+ {
258+ return { 8000 , 11025 , 12000 , 16000 , 22050 , 24000 , 32000 , 44100 , 48000 };
259+ }
260+
261+ } // namespace yup
0 commit comments