@@ -103,6 +103,83 @@ class Mp3AudioFormatReader : public AudioFormatReader
103103 YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Mp3AudioFormatReader)
104104};
105105
106+ #if YUP_MODULE_AVAILABLE_hmp3_library
107+ static E_CONTROL makeMp3EncoderControl (double sampleRate,
108+ int numberOfChannels,
109+ int qualityOptionIndex)
110+ {
111+ E_CONTROL ec = {};
112+ ec.mode = (numberOfChannels == 1 ) ? 3 : 1 ;
113+ ec.bitrate = -1 ;
114+ ec.samprate = (int ) sampleRate;
115+ ec.nsbstereo = -1 ;
116+ ec.filter_select = -1 ;
117+ ec.nsb_limit = -1 ;
118+ ec.freq_limit = 24000 ;
119+ ec.cr_bit = 1 ;
120+ ec.original = 1 ;
121+ ec.layer = 3 ;
122+ ec.hf_flag = 0 ;
123+ ec.vbr_flag = 1 ;
124+ ec.vbr_mnr = jlimit (0 , 150 , qualityOptionIndex > 0 ? qualityOptionIndex : 50 );
125+ ec.vbr_br_limit = 160 ;
126+ ec.chan_add_f0 = 24000 ;
127+ ec.chan_add_f1 = 24000 ;
128+ ec.sparse_scale = -1 ;
129+ ec.vbr_delta_mnr = 0 ;
130+ ec.cpu_select = 0 ;
131+ ec.quick = -1 ;
132+ ec.test1 = -1 ;
133+ ec.test2 = 0 ;
134+ ec.test3 = 0 ;
135+ ec.short_block_threshold = 700 ;
136+
137+ for (int i = 0 ; i < 21 ; ++i)
138+ ec.mnr_adjust [i] = 0 ;
139+
140+ return ec;
141+ }
142+
143+ class Mp3AudioFormatWriter : public AudioFormatWriter
144+ {
145+ public:
146+ Mp3AudioFormatWriter (OutputStream* destStream,
147+ double sampleRate,
148+ int numberOfChannels,
149+ int bitsPerSample,
150+ const StringPairArray& metadataValues,
151+ int qualityOptionIndex);
152+ ~Mp3AudioFormatWriter () override ;
153+
154+ bool write (const float * const * samplesToWrite, int numSamples) override ;
155+ bool flush () override ;
156+
157+ bool isValid () const { return isOpen; }
158+
159+ private:
160+ bool encodeAvailableInput ();
161+ void compactPcmBuffer ();
162+
163+ CMp3Enc encoder;
164+ E_CONTROL control = {};
165+
166+ std::vector<uint8> pcmBuffer;
167+ size_t pcmReadOffset = 0 ;
168+
169+ HeapBlock<float > interleavedBuffer;
170+ size_t interleavedCapacity = 0 ;
171+
172+ std::vector<uint8> outputBuffer;
173+ std::vector<uint8> zeroBuffer;
174+
175+ int minInputBytes = 0 ;
176+ int64 framesExpected = 0 ;
177+ bool isOpen = false ;
178+
179+ YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Mp3AudioFormatWriter)
180+ };
181+ #endif
182+
106183Mp3AudioFormatReader::Mp3AudioFormatReader (InputStream* sourceStream)
107184 : AudioFormatReader (sourceStream, " MP3 file" )
108185{
@@ -211,6 +288,150 @@ bool Mp3AudioFormatReader::readSamples (float* const* destChannels,
211288 return framesRead > 0 ;
212289}
213290
291+ #if YUP_MODULE_AVAILABLE_hmp3_library
292+ Mp3AudioFormatWriter::Mp3AudioFormatWriter (OutputStream* destStream,
293+ double sampleRate,
294+ int numberOfChannels,
295+ int bitsPerSample,
296+ const StringPairArray& metadataValues,
297+ int qualityOptionIndex)
298+ : AudioFormatWriter (destStream, " MP3 file" , sampleRate, numberOfChannels, bitsPerSample)
299+ {
300+ ignoreUnused (metadataValues);
301+
302+ control = makeMp3EncoderControl (sampleRate, numberOfChannels, qualityOptionIndex);
303+ minInputBytes = encoder.MP3_audio_encode_init (&control, 32 , 1 , 0 , 0 );
304+
305+ if (minInputBytes > 0 )
306+ {
307+ outputBuffer.resize (128u * 1024u );
308+ zeroBuffer.resize ((size_t ) minInputBytes, 0 );
309+ isOpen = true ;
310+ }
311+ }
312+
313+ Mp3AudioFormatWriter::~Mp3AudioFormatWriter ()
314+ {
315+ flush ();
316+ }
317+
318+ bool Mp3AudioFormatWriter::write (const float * const * samplesToWrite, int numSamples)
319+ {
320+ if (! isOpen || numSamples <= 0 )
321+ return false ;
322+
323+ const auto numChannels = getNumChannels ();
324+ const size_t totalSamples = (size_t ) numSamples * (size_t ) numChannels;
325+
326+ if (totalSamples > interleavedCapacity)
327+ {
328+ interleavedCapacity = totalSamples;
329+ interleavedBuffer.allocate (interleavedCapacity, false );
330+ }
331+
332+ using SourceFormat = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>;
333+ using DestFormat = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>;
334+
335+ AudioData::interleaveSamples (AudioData::NonInterleavedSource<SourceFormat> { samplesToWrite, (int ) numChannels },
336+ AudioData::InterleavedDest<DestFormat> { interleavedBuffer.getData (), (int ) numChannels },
337+ numSamples);
338+
339+ const size_t bytesToAdd = totalSamples * sizeof (float );
340+ const auto * bytes = reinterpret_cast <const uint8*> (interleavedBuffer.getData ());
341+ pcmBuffer.insert (pcmBuffer.end (), bytes, bytes + bytesToAdd);
342+
343+ return encodeAvailableInput ();
344+ }
345+
346+ bool Mp3AudioFormatWriter::flush ()
347+ {
348+ if (! isOpen)
349+ return false ;
350+
351+ if (minInputBytes > 0 )
352+ {
353+ const size_t availableBytes = pcmBuffer.size () - pcmReadOffset;
354+ if (availableBytes > 0 && availableBytes < (size_t ) minInputBytes)
355+ {
356+ const size_t padBytes = (size_t ) minInputBytes - availableBytes;
357+ pcmBuffer.insert (pcmBuffer.end (), padBytes, 0 );
358+ }
359+ }
360+
361+ if (! encodeAvailableInput ())
362+ return false ;
363+
364+ int64 expectedFrames = framesExpected;
365+ if (control.samprate < 32000 )
366+ expectedFrames *= 2 ;
367+
368+ int safetyCounter = 0 ;
369+ while (encoder.L3_audio_encode_get_frames () < (unsigned int ) expectedFrames && safetyCounter < 4096 )
370+ {
371+ auto io = encoder.MP3_audio_encode (zeroBuffer.data (), outputBuffer.data ());
372+ if (io.out_bytes > 0 )
373+ {
374+ if (! output->write (outputBuffer.data (), (size_t ) io.out_bytes ))
375+ return false ;
376+ }
377+
378+ if (io.in_bytes <= 0 && io.out_bytes <= 0 )
379+ break ;
380+
381+ ++safetyCounter;
382+ }
383+
384+ return true ;
385+ }
386+
387+ bool Mp3AudioFormatWriter::encodeAvailableInput ()
388+ {
389+ if (minInputBytes <= 0 || pcmBuffer.size () <= pcmReadOffset)
390+ return true ;
391+
392+ while (pcmBuffer.size () - pcmReadOffset >= (size_t ) minInputBytes)
393+ {
394+ auto io = encoder.MP3_audio_encode (pcmBuffer.data () + pcmReadOffset, outputBuffer.data ());
395+ if (io.in_bytes <= 0 && io.out_bytes <= 0 )
396+ break ;
397+
398+ ++framesExpected;
399+
400+ if (io.in_bytes > 0 )
401+ pcmReadOffset += (size_t ) io.in_bytes ;
402+
403+ if (io.out_bytes > 0 )
404+ {
405+ if (! output->write (outputBuffer.data (), (size_t ) io.out_bytes ))
406+ return false ;
407+ }
408+
409+ compactPcmBuffer ();
410+ }
411+
412+ return true ;
413+ }
414+
415+ void Mp3AudioFormatWriter::compactPcmBuffer ()
416+ {
417+ if (pcmReadOffset == 0 )
418+ return ;
419+
420+ if (pcmReadOffset >= pcmBuffer.size ())
421+ {
422+ pcmBuffer.clear ();
423+ pcmReadOffset = 0 ;
424+ return ;
425+ }
426+
427+ if (pcmReadOffset > 4096 )
428+ {
429+ pcmBuffer.erase (pcmBuffer.begin (), pcmBuffer.begin () + (ptrdiff_t ) pcmReadOffset);
430+ pcmReadOffset = 0 ;
431+ }
432+ }
433+ #endif
434+
214435} // namespace
215436
216437// ==============================================================================
@@ -249,7 +470,33 @@ std::unique_ptr<AudioFormatWriter> Mp3AudioFormat::createWriterFor (OutputStream
249470 const StringPairArray& metadataValues,
250471 int qualityOptionIndex)
251472{
252- // MP3 encoding is not implemented in this version
473+ #if YUP_MODULE_AVAILABLE_hmp3_library
474+ if (streamToWriteTo == nullptr )
475+ return nullptr ;
476+
477+ if (numberOfChannels < 1 || numberOfChannels > 2 )
478+ return nullptr ;
479+
480+ if (sampleRate < 8000.0 || sampleRate > 48000.0 )
481+ return nullptr ;
482+
483+ auto writer = std::make_unique<Mp3AudioFormatWriter> (streamToWriteTo,
484+ sampleRate,
485+ numberOfChannels,
486+ bitsPerSample,
487+ metadataValues,
488+ qualityOptionIndex);
489+ if (writer->isValid ())
490+ return writer;
491+ #else
492+ ignoreUnused (streamToWriteTo,
493+ sampleRate,
494+ numberOfChannels,
495+ bitsPerSample,
496+ metadataValues,
497+ qualityOptionIndex);
498+ #endif
499+
253500 return nullptr ;
254501}
255502
@@ -263,4 +510,4 @@ Array<int> Mp3AudioFormat::getPossibleSampleRates() const
263510 return { 8000 , 11025 , 12000 , 16000 , 22050 , 24000 , 32000 , 44100 , 48000 };
264511}
265512
266- } // namespace yup
513+ } // namespace yup
0 commit comments