FastLED 3.9.15
Loading...
Searching...
No Matches
mp3.cpp.hpp
Go to the documentation of this file.
1#include "fl/codec/mp3.h"
2#include "fl/stl/bit_cast.h"
3#include "fl/stl/cstring.h"
4
5// Include Helix MP3 decoder internal API (in fl::third_party namespace)
6// IWYU pragma: begin_keep
8#include "fl/stl/noexcept.h"
9// IWYU pragma: end_keep
10
11namespace fl {
12namespace third_party {
13
14// Maximum PCM output: 1152 samples/channel * 2 channels = 2304 samples
15constexpr fl::size MAX_PCM_SAMPLES = 2304;
16
17// Mp3HelixDecoder implementation
22
26
28 if (mDecoder) {
29 return true; // Already initialized
30 }
31
32 // Initialize Helix decoder
34 if (!mDecoder) {
35 return false;
36 }
37
38 // Allocate PCM buffer
39 mPcmBuffer.reset(new fl::i16[MAX_PCM_SAMPLES]); // ok bare allocation (array new)
40 if (!mPcmBuffer) {
41 MP3FreeDecoder(static_cast<HMP3Decoder>(mDecoder));
42 mDecoder = nullptr;
43 return false;
44 }
45
46 return true;
47}
48
50 if (mDecoder) {
51 MP3FreeDecoder(static_cast<HMP3Decoder>(mDecoder));
52 mDecoder = nullptr;
53 }
54
55 mPcmBuffer.reset();
56
57 fl::memset(&mFrameInfo, 0, sizeof(mFrameInfo));
58}
59
60int Mp3HelixDecoder::findSyncWord(const fl::u8* buf, fl::size len) {
61 int offset = MP3FindSyncWord(buf, static_cast<int>(len));
62 return offset;
63}
64
65int Mp3HelixDecoder::decodeFrame(const fl::u8** inbuf, fl::size* bytes_left) {
66 if (!mDecoder || !mPcmBuffer) {
68 }
69
70 // Decode one frame
71 int result = MP3Decode(
72 static_cast<HMP3Decoder>(mDecoder),
73 inbuf,
74 bytes_left,
76 0 // useSize = 0 (use default)
77 );
78
79 if (result == ERR_MP3_NONE) {
80 // Get frame info
81 MP3FrameInfo helix_info;
82 MP3GetLastFrameInfo(static_cast<HMP3Decoder>(mDecoder), &helix_info);
83
84 mFrameInfo.bitrate = helix_info.bitrate;
85 mFrameInfo.nChans = helix_info.nChans;
86 mFrameInfo.samprate = helix_info.samprate;
87 mFrameInfo.bitsPerSample = helix_info.bitsPerSample;
88 mFrameInfo.outputSamps = helix_info.outputSamps;
89 mFrameInfo.layer = helix_info.layer;
90 mFrameInfo.version = helix_info.version;
91 }
92
93 return result;
94}
95
98
99 decode(data, len, [&](const Mp3Frame& frame) {
100 // Convert stereo to mono by averaging channels
101 if (frame.channels == 2) {
102 fl::vector<fl::i16> mono_pcm;
103 mono_pcm.reserve(frame.samples);
104
105 for (int i = 0; i < frame.samples; i++) {
106 fl::i32 left = frame.pcm[i * 2];
107 fl::i32 right = frame.pcm[i * 2 + 1];
108 fl::i32 avg = (left + right) / 2;
109 mono_pcm.push_back(static_cast<fl::i16>(avg));
110 }
111
112 audio::Sample sample(mono_pcm);
113 samples.push_back(sample);
114 } else {
115 // Mono audio - use directly
117 samples.push_back(sample);
118 }
119 });
120
121 return samples;
122}
123
124// Mp3StreamDecoderImpl - internal implementation of streaming MP3 decoder
126 public:
129
130 bool begin(fl::filebuf_ptr stream);
131 void end();
132 bool isReady() const { return mStream != nullptr && mDecoder != nullptr; }
133 bool hasError(fl::string* msg = nullptr) const;
134 bool decodeNextFrame(audio::Sample* out_sample);
135 fl::size getPosition() const { return mBytesProcessed; }
136 void reset();
137 Mp3Info getInfo() const { return mInfo; }
138
139 private:
140 static constexpr fl::size BUFFER_SIZE = 4096;
141
142 bool fillBuffer();
143 bool findAndDecodeFrame(audio::Sample* out_sample);
144
148 fl::size mBufferPos;
156};
157
164
168
170 if (!stream) {
171 mErrorMsg = "Invalid stream provided";
172 mHasError = true;
173 return false;
174 }
175
176 mStream = stream;
178 if (!mDecoder->init()) {
179 mErrorMsg = "Failed to initialize MP3 decoder";
180 mHasError = true;
181 mDecoder.reset();
182 return false;
183 }
184
185 mBuffer.resize(BUFFER_SIZE);
186 mBufferPos = 0;
187 mBufferFilled = 0;
188 mBytesProcessed = 0;
189 mHasError = false;
190 mEndOfStream = false;
191 mHasDecodedFirstFrame = false;
192
193 return true;
194}
195
197 mDecoder.reset();
198 if (mStream) {
199 mStream->close();
200 mStream = nullptr;
201 }
202 mBuffer.clear();
203}
204
206 if (msg && mHasError) {
207 *msg = mErrorMsg;
208 }
209 return mHasError;
210}
211
213 if (mDecoder) {
214 mDecoder->reset();
215 mDecoder->init();
216 }
217 mBufferPos = 0;
218 mBufferFilled = 0;
219 mBytesProcessed = 0;
220 mHasError = false;
221 mEndOfStream = false;
222 mHasDecodedFirstFrame = false;
223}
224
226 // Shift remaining data to beginning of buffer
227 if (mBufferPos > 0 && mBufferFilled > mBufferPos) {
228 fl::size remaining = mBufferFilled - mBufferPos;
229 for (fl::size i = 0; i < remaining; i++) {
230 mBuffer[i] = mBuffer[mBufferPos + i];
231 }
232 mBufferFilled = remaining;
233 mBufferPos = 0;
234 } else if (mBufferPos >= mBufferFilled) {
235 mBufferPos = 0;
236 mBufferFilled = 0;
237 }
238
239 // Fill the rest of the buffer from stream
240 fl::size spaceAvailable = BUFFER_SIZE - mBufferFilled;
241 if (spaceAvailable > 0 && mStream && mStream->available(1)) {
242 fl::size bytesRead = mStream->read(mBuffer.data() + mBufferFilled, spaceAvailable);
243 mBufferFilled += bytesRead;
244 return bytesRead > 0;
245 }
246
247 return mBufferFilled > mBufferPos;
248}
249
251 if (!mDecoder) {
252 return false;
253 }
254
255 // Try to decode from current buffer
256 const fl::u8* inptr = mBuffer.data() + mBufferPos;
257 fl::size bytes_left = mBufferFilled - mBufferPos;
258
259 if (bytes_left == 0) {
260 return false;
261 }
262
263 // Find sync word
264 int offset = mDecoder->findSyncWord(inptr, bytes_left);
265 if (offset < 0) {
266 // No sync word found, consume buffer and try again
268 return false;
269 }
270
271 inptr += offset;
272 bytes_left -= offset;
274
275 // Try to decode one frame
276 const fl::u8* decode_ptr = inptr;
277 fl::size decode_bytes = bytes_left;
278
279 int result = mDecoder->decodeFrame(&decode_ptr, &decode_bytes);
280
281 // Update buffer position based on how many bytes were consumed
282 fl::size consumed = (decode_ptr - inptr);
283 mBufferPos += consumed;
284 mBytesProcessed += consumed;
285
286 if (result == 0) {
287 // Successfully decoded a frame
288 Mp3Frame frame;
289 frame.pcm = mDecoder->mPcmBuffer.get();
290 frame.samples = mDecoder->mFrameInfo.outputSamps / mDecoder->mFrameInfo.nChans;
291 frame.channels = mDecoder->mFrameInfo.nChans;
292 frame.sample_rate = mDecoder->mFrameInfo.samprate;
293 frame.bitrate = mDecoder->mFrameInfo.bitrate;
294 frame.version = mDecoder->mFrameInfo.version;
295 frame.layer = mDecoder->mFrameInfo.layer;
296
297 // Update stream info on first successful decode
299 mInfo.sampleRate = frame.sample_rate;
300 mInfo.channels = static_cast<fl::u8>(frame.channels);
301 mInfo.bitrate = frame.bitrate;
302 mInfo.version = static_cast<fl::u8>(frame.version);
303 mInfo.layer = static_cast<fl::u8>(frame.layer);
304 mInfo.isValid = true;
306 }
307
308 // Convert to audio::Sample (convert stereo to mono if needed)
309 if (frame.channels == 2) {
310 fl::vector<fl::i16> mono_pcm;
311 mono_pcm.reserve(frame.samples);
312
313 for (int i = 0; i < frame.samples; i++) {
314 fl::i32 left = frame.pcm[i * 2];
315 fl::i32 right = frame.pcm[i * 2 + 1];
316 fl::i32 avg = (left + right) / 2;
317 mono_pcm.push_back(static_cast<fl::i16>(avg));
318 }
319
320 *out_sample = audio::Sample(mono_pcm);
321 } else {
322 // Mono audio - use directly
323 *out_sample = audio::Sample(fl::span<const fl::i16>(frame.pcm, frame.samples));
324 }
325
326 return true;
327 }
328
329 return false;
330}
331
333 if (!isReady()) {
334 mErrorMsg = "Decoder not ready";
335 mHasError = true;
336 return false;
337 }
338
339 if (mEndOfStream) {
340 return false;
341 }
342
343 // Try to decode from existing buffer
344 if (findAndDecodeFrame(out_sample)) {
345 return true;
346 }
347
348 // Need more data - try to fill buffer and decode again
349 while (fillBuffer()) {
350 if (findAndDecodeFrame(out_sample)) {
351 return true;
352 }
353 }
354
355 // No more data available
356 mEndOfStream = true;
357 return false;
358}
359
360} // namespace third_party
361
362// Mp3Decoder implementation
363Mp3Decoder::Mp3Decoder() : mImpl(fl::make_unique<third_party::Mp3StreamDecoderImpl>()) {}
364
366
368 return mImpl->begin(stream);
369}
370
372 mImpl->end();
373}
374
376 return mImpl->isReady();
377}
378
380 return mImpl->hasError(msg);
381}
382
384 return mImpl->decodeNextFrame(out_sample);
385}
386
387fl::size Mp3Decoder::getPosition() const {
388 return mImpl->getPosition();
389}
390
392 mImpl->reset();
393}
394
396 return mImpl->getInfo();
397}
398
399// Mp3 factory implementation
401 FL_UNUSED(error_message);
403}
404
406 // MP3 decoder is available on all platforms
407 return true;
408}
409
411 Mp3Info info;
412
413 // Validate input data
414 if (data.empty()) {
415 if (error_message) {
416 *error_message = "Empty MP3 data";
417 }
418 return info; // returns invalid info
419 }
420
421 // Minimum size check - need at least an MP3 frame header (4 bytes) + some data
422 if (data.size() < 128) {
423 if (error_message) {
424 *error_message = "MP3 data too small";
425 }
426 return info;
427 }
428
429 // Look for MP3 sync word (11 bits set: 0xFFE or 0xFFF)
430 bool foundSync = false;
431 fl::size syncOffset = 0;
432
433 for (fl::size i = 0; i <= data.size() - 4; i++) {
434 if (data[i] == 0xFF && (data[i + 1] & 0xE0) == 0xE0) {
435 foundSync = true;
436 syncOffset = i;
437 break;
438 }
439 }
440
441 if (!foundSync) {
442 if (error_message) {
443 *error_message = "Invalid MP3 stream - no sync word found";
444 }
445 return info;
446 }
447
448 // Use the decoder to parse the first frame
450 if (!decoder.init()) {
451 if (error_message) {
452 *error_message = "Failed to initialize MP3 decoder";
453 }
454 return info;
455 }
456
457 const fl::u8* inptr = data.data() + syncOffset;
458 fl::size bytes_left = data.size() - syncOffset;
459
460 // Try to decode first frame to get metadata
461 int offset = decoder.findSyncWord(inptr, bytes_left);
462 if (offset >= 0) {
463 inptr += offset;
464 bytes_left -= offset;
465
466 int result = decoder.decodeFrame(&inptr, &bytes_left);
467 if (result == 0) {
468 // Successfully decoded - extract metadata
469 info.sampleRate = decoder.mFrameInfo.samprate;
470 info.channels = static_cast<fl::u8>(decoder.mFrameInfo.nChans);
471 info.bitrate = decoder.mFrameInfo.bitrate;
472 info.version = static_cast<fl::u8>(decoder.mFrameInfo.version);
473 info.layer = static_cast<fl::u8>(decoder.mFrameInfo.layer);
474 info.isValid = true;
475 } else if (error_message) {
476 *error_message = "Failed to decode MP3 frame header";
477 }
478 } else if (error_message) {
479 *error_message = "Failed to find MP3 sync word";
480 }
481
482 return info;
483}
484
485} // namespace fl
static Mp3Info parseMp3Info(fl::span< const fl::u8 > data, fl::string *error_message=nullptr)
Definition mp3.cpp.hpp:410
static bool isSupported()
Definition mp3.cpp.hpp:405
static Mp3DecoderPtr createDecoder(fl::string *error_message=nullptr)
Definition mp3.cpp.hpp:400
Mp3Decoder() FL_NOEXCEPT
Definition mp3.cpp.hpp:363
bool begin(fl::filebuf_ptr stream)
Definition mp3.cpp.hpp:367
bool isReady() const
Definition mp3.cpp.hpp:375
bool hasError(fl::string *msg=nullptr) const
Definition mp3.cpp.hpp:379
~Mp3Decoder() FL_NOEXCEPT
bool decodeNextFrame(audio::Sample *out_sample)
Definition mp3.cpp.hpp:383
Mp3Info getInfo() const
Definition mp3.cpp.hpp:395
fl::size getPosition() const
Definition mp3.cpp.hpp:387
fl::unique_ptr< fl::third_party::Mp3StreamDecoderImpl > mImpl
Definition mp3.h:168
constexpr bool empty() const FL_NOEXCEPT
Definition span.h:510
const T * data() const FL_NOEXCEPT
Definition span.h:461
constexpr fl::size size() const FL_NOEXCEPT
Definition span.h:458
int decodeFrame(const fl::u8 **inbuf, fl::size *bytes_left)
Definition mp3.cpp.hpp:65
int findSyncWord(const fl::u8 *buf, fl::size len)
Definition mp3.cpp.hpp:60
fl::unique_ptr< fl::i16[]> mPcmBuffer
Definition mp3.h:117
int decode(const fl::u8 *data, fl::size len, Fn on_frame)
Definition mp3.h:64
fl::vector< audio::Sample > decodeToAudioSamples(const fl::u8 *data, fl::size len)
Definition mp3.cpp.hpp:96
bool begin(fl::filebuf_ptr stream)
Definition mp3.cpp.hpp:169
bool findAndDecodeFrame(audio::Sample *out_sample)
Definition mp3.cpp.hpp:250
static constexpr fl::size BUFFER_SIZE
Definition mp3.cpp.hpp:140
fl::unique_ptr< Mp3HelixDecoder > mDecoder
Definition mp3.cpp.hpp:146
bool hasError(fl::string *msg=nullptr) const
Definition mp3.cpp.hpp:205
bool decodeNextFrame(audio::Sample *out_sample)
Definition mp3.cpp.hpp:332
void reserve(fl::size n) FL_NOEXCEPT
Definition vector.h:591
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:624
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
@ ERR_MP3_NONE
Definition mp3dec.h:91
@ ERR_MP3_NULL_POINTER
Definition mp3dec.h:96
void * HMP3Decoder
Definition mp3dec.h:88
struct _MP3FrameInfo MP3FrameInfo
int outputSamps
Definition mp3dec.h:113
int bitsPerSample
Definition mp3dec.h:112
unsigned char u8
Definition s16x16x4.h:132
unsigned char u8
Definition coder.h:132
int MP3FindSyncWord(const unsigned char *buf, int nBytes) FL_NOEXCEPT
Definition mp3dec.hpp:116
void MP3FreeDecoder(HMP3Decoder hMP3Decoder) FL_NOEXCEPT
Definition mp3dec.hpp:93
HMP3Decoder MP3InitDecoder(void) FL_NOEXCEPT
Definition mp3dec.hpp:72
int MP3Decode(HMP3Decoder hMP3Decoder, const unsigned char **inbuf, size_t *bytesLeft, short *outbuf, int useSize) FL_NOEXCEPT
Definition mp3dec.hpp:292
void MP3GetLastFrameInfo(HMP3Decoder hMP3Decoder, MP3FrameInfo *mp3FrameInfo) FL_NOEXCEPT
Definition mp3dec.hpp:196
constexpr fl::size MAX_PCM_SAMPLES
Definition mp3.cpp.hpp:15
const fl::i16 * pcm
Definition mp3.h:39
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
Definition unique_ptr.h:261
void * memset(void *s, int c, size_t n) FL_NOEXCEPT
CRGB sample(const CRGB *grid, const XYMap &xyMap, float x, float y, SampleMode mode)
Sample a pixel from a 2D CRGB grid at floating-point coordinates.
Definition sample.cpp.hpp:9
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
fl::shared_ptr< filebuf > filebuf_ptr
Definition idecoder.h:15
fl::shared_ptr< Mp3Decoder > Mp3DecoderPtr
Definition file_system.h:19
To bit_cast(const From &from) FL_NOEXCEPT
Definition bit_cast.h:48
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_UNUSED(x)
#define FL_NOEXCEPT
fl::u8 layer
Definition mp3.h:22
fl::u8 channels
Definition mp3.h:18
fl::u8 version
Definition mp3.h:21
fl::u32 bitrate
Definition mp3.h:19
fl::u32 sampleRate
Definition mp3.h:17
bool isValid
Definition mp3.h:23