arduino-audio-tools
Loading...
Searching...
No Matches
HeaderParserMP3.h
1#pragma once
2#include "AudioTools/CoreAudio/AudioBasic/StrView.h"
3
4namespace audio_tools {
5
26 // MPEG audio frame header fields parsed from 4 serialized bytes
27 struct FrameHeader {
28 static const unsigned int SERIALIZED_SIZE = 4;
29
30 enum class MPEGVersionID : unsigned {
31 MPEG_2_5 = 0b00,
32 INVALID = 0b01, // reserved
33 MPEG_2 = 0b10,
34 MPEG_1 = 0b11,
35 };
36
37 enum class LayerID : unsigned {
38 INVALID = 0b00, // reserved
39 LAYER_3 = 0b01,
40 LAYER_2 = 0b10,
41 LAYER_1 = 0b11,
42 };
43
44 enum class ChannelModeID : unsigned {
45 STEREO = 0b00,
46 JOINT = 0b01, // joint stereo
47 DUAL = 0b10, // dual channel (2 mono channels)
48 SINGLE = 0b11, // single channel (mono)
49 };
50
51 enum class EmphasisID : unsigned {
52 NONE = 0b00,
53 MS_50_15 = 0b01,
54 INVALID = 0b10,
55 CCIT_J17 = 0b11,
56 };
57
58 enum SpecialBitrate { INVALID_BITRATE = -8000, ANY = 0 };
59 enum SpecialSampleRate { RESERVED = 0 };
60
61 // Parsed fields
62 MPEGVersionID AudioVersion = MPEGVersionID::INVALID;
63 LayerID Layer = LayerID::INVALID;
64 bool Protection = false;
65 uint8_t BitrateIndex = 0; // 0..15
66 uint8_t SampleRateIndex = 0; // 0..3
67 bool Padding = false;
68 bool Private = false;
69 ChannelModeID ChannelMode = ChannelModeID::STEREO;
70 uint8_t ExtentionMode = 0; // 0..3
71 bool Copyright = false;
72 bool Original = false;
73 EmphasisID Emphasis = EmphasisID::NONE;
74
75 // Decode 4 bytes into the fields above. Returns false if sync invalid.
76 static bool decode(const uint8_t* b, FrameHeader& out) {
77 if (b == nullptr) return false;
78 if (!(b[0] == 0xFF && (b[1] & 0xE0) == 0xE0)) return false; // 11-bit sync
79
80 uint8_t b1 = b[1];
81 uint8_t b2 = b[2];
82 uint8_t b3 = b[3];
83
84 out.AudioVersion = static_cast<MPEGVersionID>((b1 >> 3) & 0x03);
85 out.Layer = static_cast<LayerID>((b1 >> 1) & 0x03);
86 out.Protection = !(b1 & 0x01); // 0 means protected (CRC present)
87
88 out.BitrateIndex = (b2 >> 4) & 0x0F;
89 out.SampleRateIndex = (b2 >> 2) & 0x03;
90 out.Padding = (b2 >> 1) & 0x01;
91 out.Private = (b2 & 0x01) != 0;
92
93 out.ChannelMode = static_cast<ChannelModeID>((b3 >> 6) & 0x03);
94 out.ExtentionMode = (b3 >> 4) & 0x03;
95 out.Copyright = (b3 >> 3) & 0x01;
96 out.Original = (b3 >> 2) & 0x01;
97 out.Emphasis = static_cast<EmphasisID>(b3 & 0x03);
98 return true;
99 }
100
101 signed int getBitRate() const {
102 // version, layer, bit index
103 static const signed char rateTable[4][4][16] = {
104 // version[00] = MPEG_2_5
105 {
106 // layer[00] = INVALID
107 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
108 // layer[01] = LAYER_3
109 {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, -1},
110 // layer[10] = LAYER_2
111 {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, -1},
112 // layer[11] = LAYER_1
113 {0, 4, 6, 7, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, -1},
114 },
115
116 // version[01] = INVALID
117 {
118 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
119 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
120 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
121 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
122 },
123
124 // version[10] = MPEG_2
125 {
126 // layer[00] = INVALID
127 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
128 // layer[01] = LAYER_3
129 {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, -1},
130 // layer[10] = LAYER_2
131 {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, -1},
132 // layer[11] = LAYER_1
133 {0, 4, 6, 7, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, -1},
134 },
135
136 // version[11] = MPEG_1
137 {
138 // layer[00] = INVALID
139 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
140 // layer[01] = LAYER_3
141 {0, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, -1},
142 // layer[10] = LAYER_2
143 {0, 4, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, -1},
144 // layer[11] = LAYER_1
145 {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, -1},
146 },
147 };
148 signed char rate_byte = rateTable[(int)AudioVersion][(int)Layer][(int)BitrateIndex];
149 if (rate_byte == -1) {
150 LOGE("Unsupported bitrate");
151 return 0;
152 }
153 return rate_byte * 8000;
154 }
155
156 unsigned short getSampleRate() const {
157 // version, sample rate index
158 static const unsigned short rateTable[4][4] = {
159 // version[00] = MPEG_2_5
160 {11025, 12000, 8000, 0},
161 // version[01] = INVALID
162 {0, 0, 0, 0},
163 // version[10] = MPEG_2
164 {22050, 24000, 16000, 0},
165 // version[11] = MPEG_1
166 {44100, 48000, 32000, 0},
167 };
168
169 return rateTable[(int)AudioVersion][(int)SampleRateIndex];
170 }
171
172 int getFrameLength() const {
173 int sample_rate = getSampleRate();
174 if (sample_rate == 0) return 0;
175 int value = (AudioVersion == FrameHeader::MPEGVersionID::MPEG_1) ? 144 : 72;
176 return int((value * getBitRate() / sample_rate) + (Padding ? 1 : 0));
177 }
178 };
179
180 public:
182 HeaderParserMP3() = default;
183
185 HeaderParserMP3(Print& output, int bufferSize=2048) : p_output(&output), buffer_size(bufferSize) {}
186
187 void setOutput(Print& output) {
188 p_output = &output;
189 }
190
191 void resize(int size){buffer_size = size;}
192
194 size_t write(const uint8_t* data, size_t len) {
195 if (buffer.size() < buffer_size) buffer.resize(buffer_size);
196
197 for (int i = 0; i < len; i++) {
198 buffer.write(data[i]);
199 if (buffer.isFull()) {
200 while (processBuffer()) ;
201 }
202 }
203
204 return len;
205 }
206
207 void flush() {
208 if (p_output == nullptr) return;
209 while (processBuffer()) ;
210 }
211
213 bool isValid() { return last_frame_size > 0;}
214
216 bool isValid(const uint8_t* data, int len) {
217 if (data == nullptr || len < 10) {
218 LOGE("Invalid input data or too small");
219 return false;
220 }
221
222 memset(&header, 0, sizeof(header));
223 int valid_frames_found = 0;
224 int consecutive_frames = 0;
225 const int MIN_FRAMES_TO_VALIDATE = 3; // Require at least 3 consecutive valid frames
226 const int MAX_SEARCH_DISTANCE = 8192; // Limit search to prevent endless loops
227
228 // Check for ID3v2 tag at beginning
229 if (len >= 10 && memcmp(data, "ID3", 3) == 0) {
230 LOGI("ID3v2 tag found");
231 // Skip ID3v2 tag to find actual audio data
232 int id3_size = ((data[6] & 0x7F) << 21) | ((data[7] & 0x7F) << 14) |
233 ((data[8] & 0x7F) << 7) | (data[9] & 0x7F);
234 int audio_start = 10 + id3_size;
235 if (audio_start < len) {
236 return isValid(data + audio_start, len - audio_start);
237 }
238 return true; // Valid ID3 tag, assume MP3
239 }
240
241 // Look for first frame sync
242 int sync_pos = seekFrameSync(data, min(len, MAX_SEARCH_DISTANCE));
243 if (sync_pos == -1) {
244 LOGE("No frame sync found in first %d bytes", MAX_SEARCH_DISTANCE);
245 return false;
246 }
247
248 // Quick check for VBR headers (Xing/Info/VBRI)
249 if (contains(data + sync_pos, "Xing", len - sync_pos) ||
250 contains(data + sync_pos, "Info", len - sync_pos) ||
251 contains(data + sync_pos, "VBRI", len - sync_pos)) {
252 LOGI("VBR header found (Xing/Info/VBRI)");
253 return true;
254 }
255
256 // Validate multiple consecutive frames for higher confidence
257 int current_pos = sync_pos;
258 FrameHeader first_header;
259 bool first_header_set = false;
260
261 while (current_pos < len && (current_pos - sync_pos) < MAX_SEARCH_DISTANCE) {
262 int len_available = len - current_pos;
263
264 // Need at least header size
265 if (len_available < (int)FrameHeader::SERIALIZED_SIZE) {
266 LOGD("Not enough data for header at position %d", current_pos);
267 break;
268 }
269
270 // Read and validate frame header
271 FrameHeader temp_header;
272 if (!FrameHeader::decode(data + current_pos, temp_header) ||
273 validateFrameHeader(temp_header) != FrameReason::VALID) {
274 LOGD("Invalid frame header at position %d", current_pos);
275 consecutive_frames = 0;
276 // Look for next sync
277 int next_sync_off = seekFrameSync(data + current_pos + 1, len - current_pos - 1);
278 if (next_sync_off == -1) break;
279 current_pos = current_pos + 1 + next_sync_off; // Adjust for offset
280 continue;
281 }
282
283 // Calculate frame length
284 int frame_len = temp_header.getFrameLength();
285 if (frame_len <= 0 || frame_len > 4096) {
286 LOGD("Invalid frame length %d at position %d", frame_len, current_pos);
287 consecutive_frames = 0;
288 current_pos++;
289 continue;
290 }
291
292 // For first valid frame, store header for consistency checking
293 if (!first_header_set) {
294 first_header = temp_header;
295 first_header_set = true;
296 header = temp_header; // Store for external access
297
298 // For small buffers, do additional single-frame validation
299 if (len < 1024) {
300 // Verify this looks like a reasonable MP3 frame
301 if (temp_header.getSampleRate() == 0 || temp_header.getBitRate() <= 0) {
302 LOGD("Invalid audio parameters in frame at position %d", current_pos);
303 first_header_set = false;
304 consecutive_frames = 0;
305 current_pos++;
306 continue;
307 }
308
309 // Check if frame length is reasonable for the given bitrate
310 int expected_frame_size = (temp_header.AudioVersion == FrameHeader::MPEGVersionID::MPEG_1) ?
311 (144 * temp_header.getBitRate() / temp_header.getSampleRate()) :
312 (72 * temp_header.getBitRate() / temp_header.getSampleRate());
313 if (abs(frame_len - expected_frame_size) > expected_frame_size * 0.1) { // Allow 10% variance
314 LOGD("Frame length %d doesn't match expected %d for bitrate", frame_len, expected_frame_size);
315 first_header_set = false;
316 consecutive_frames = 0;
317 current_pos++;
318 continue;
319 }
320 }
321 } else {
322 // Check consistency with first frame (sample rate, version, layer should match in CBR)
323 if (temp_header.AudioVersion != first_header.AudioVersion ||
324 temp_header.Layer != first_header.Layer ||
325 temp_header.getSampleRate() != first_header.getSampleRate()) {
326 LOGD("Frame parameters inconsistent at position %d", current_pos);
327 // This might be VBR, but continue validation
328 }
329 }
330
331 valid_frames_found++;
332 consecutive_frames++;
333
334 // Check if we have enough data for the complete frame
335 if (len_available < frame_len) {
336 LOGD("Incomplete frame at position %d (need %d, have %d)",
337 current_pos, frame_len, len_available);
338 break;
339 }
340
341 // Look for next frame sync at expected position
342 int next_pos = current_pos + frame_len;
343 if (next_pos + 1 < len) {
344 if (seekFrameSync(data + next_pos, min(4, len - next_pos)) == 0) {
345 // Found sync at expected position
346 current_pos = next_pos;
347 continue;
348 } else {
349 LOGD("No sync at expected position %d", next_pos);
350 consecutive_frames = 0;
351 }
352 } else {
353 // End of data reached
354 break;
355 }
356
357 // If we lost sync, search for next frame
358 int next_sync = seekFrameSync(data + current_pos + 1, len - current_pos - 1);
359 if (next_sync == -1) break;
360 current_pos = current_pos + 1 + next_sync;
361 }
362
363 // Adaptive validation criteria based on available data
364 bool is_valid_mp3 = false;
365
366 if (len >= 2048) {
367 // For larger buffers, require strict consecutive frame validation
368 is_valid_mp3 = (consecutive_frames >= MIN_FRAMES_TO_VALIDATE);
369 } else if (len >= 1024) {
370 // For 1KB+ buffers, require at least 2 consecutive frames OR 3 total valid frames
371 is_valid_mp3 = (consecutive_frames >= 2) || (valid_frames_found >= MIN_FRAMES_TO_VALIDATE);
372 } else {
373 // For smaller buffers, be more lenient - 1 good frame with proper validation
374 is_valid_mp3 = (valid_frames_found >= 1) && first_header_set;
375 }
376
377 if (is_valid_mp3 && first_header_set) {
378 LOGI("-------------------");
379 LOGI("MP3 validation: VALID");
380 LOGI("Data size: %d bytes", len);
381 LOGI("Valid frames found: %d", valid_frames_found);
382 LOGI("Consecutive frames: %d", consecutive_frames);
383 if (len >= 2048) {
384 LOGI("Validation mode: STRICT (large buffer)");
385 } else if (len >= 1024) {
386 LOGI("Validation mode: MODERATE (1KB+ buffer)");
387 } else {
388 LOGI("Validation mode: LENIENT (small buffer)");
389 }
390 LOGI("Frame size: %d", getFrameLength());
391 LOGI("Sample rate: %u", getSampleRate());
392 LOGI("Bit rate: %d", getBitRate());
393 LOGI("Padding: %d", getFrameHeader().Padding);
394 LOGI("Layer: %s (0x%x)", getLayerStr(), (int)getFrameHeader().Layer);
395 LOGI("Version: %s (0x%x)", getVersionStr(), (int)getFrameHeader().AudioVersion);
396 LOGI("-------------------");
397 } else {
398 LOGI("MP3 validation: INVALID (frames: %d, consecutive: %d, size: %d)",
399 valid_frames_found, consecutive_frames, len);
400 }
401
402 return is_valid_mp3;
403 }
404
406 uint16_t getSampleRate() const {
407 return frame_header_valid ? header.getSampleRate() : 0;
408 }
409
411 int getBitRate() const {
412 return frame_header_valid ? header.getBitRate() : 0;
413 }
414
416 int getChannels() const {
417 if (!frame_header_valid) return 0;
418 // SINGLE = mono (1 channel), all others = stereo (2 channels)
419 return (header.ChannelMode == FrameHeader::ChannelModeID::SINGLE) ? 1 : 2;
420 }
421
424 return frame_header_valid ? header.getFrameLength() : 0;
425 }
426
429 size_t getPlayingTime(size_t fileSizeBytes) {
430 int bitrate = getBitRate();
431 if (bitrate == 0) return 0;
432 return fileSizeBytes / bitrate;
433 }
434
436 const char* getVersionStr() const {
437 return header.AudioVersion == FrameHeader::MPEGVersionID::MPEG_1 ? "1"
438 : header.AudioVersion == FrameHeader::MPEGVersionID::MPEG_2 ? "2"
439 : header.AudioVersion == FrameHeader::MPEGVersionID::MPEG_2_5
440 ? "2.5"
441 : "INVALID";
442 }
443
445 const char* getLayerStr() const {
446 return header.Layer == FrameHeader::LayerID::LAYER_1 ? "1"
447 : header.Layer == FrameHeader::LayerID::LAYER_2 ? "2"
448 : header.Layer == FrameHeader::LayerID::LAYER_3 ? "3"
449 : "INVALID";
450 }
451
454 if (header.Layer != FrameHeader::LayerID::LAYER_3) return 0;
455 // samples for layer 3 are fixed
456 return header.AudioVersion == FrameHeader::MPEGVersionID::MPEG_1 ? 1152 : 576;
457 }
458
461 int sample_rate = getSampleRate();
462 if (sample_rate == 0) return 0;
463 return (1000 * getSamplesPerFrame()) / sample_rate;
464 }
465
467 size_t getFrameRateHz() {
468 int time_per_frame = getTimePerFrameMs();
469 if (time_per_frame == 0) return 0;
470 return 1000 / time_per_frame;
471 }
472
473 // provides the parsed MP3 frame header
474 FrameHeader getFrameHeader() {
475 return frame_header_valid ? header : FrameHeader{};
476 }
477
479 bool hasValidFrame() const {
480 return frame_header_valid;
481 }
482
484 void reset() {
485 buffer.reset();
486 frame_header_valid = false;
487 memset(&header, 0, sizeof(header));
488 }
489
491 int findSyncWord(const uint8_t* buf, size_t nBytes, uint8_t synch = 0xFF,
492 uint8_t syncl = 0xF0) {
493 for (int i = 0; i < nBytes - 1; i++) {
494 if ((buf[i + 0] & synch) == synch && (buf[i + 1] & syncl) == syncl)
495 return i;
496 }
497 return -1;
498 }
499
500 protected:
501 FrameHeader header;
502 Print* p_output = nullptr;
503 SingleBuffer<uint8_t> buffer{0}; // Max MP3 frame ~4KB + reserves
504 bool frame_header_valid = false;
505 size_t buffer_size = 0;
506 size_t last_frame_size = 0;
507
510 bool progress = false;
511 size_t available = buffer.available();
512
513 while (available >= FrameHeader::SERIALIZED_SIZE) { // Need 4 bytes for header
514 // Get direct access to buffer data
515 uint8_t* temp_data = buffer.data();
516
517 // Find frame sync
518 int sync_pos = seekFrameSync(temp_data, available);
519 if (sync_pos == -1) {
520 // No sync found, keep last few bytes in case sync spans buffer boundary
521 size_t to_remove = (available > 3) ? available - 3 : 0;
522 if (to_remove > 0) {
523 buffer.clearArray(to_remove);
524 }
525 // Recompute available after mutation
526 available = buffer.available();
527 break;
528 }
529
530 // Remove any data before sync
531 if (sync_pos > 0) {
532 buffer.clearArray(sync_pos);
533 progress = true;
534 // Recompute available after mutation
535 available = buffer.available();
536 continue; // Check again from new position
537 }
538
539 // We have sync at position 0, try to read header
540 if (available < FrameHeader::SERIALIZED_SIZE) {
541 break; // Need more data for complete header
542 }
543
544 // Read and validate frame header
545 FrameHeader temp_header;
546 if (!FrameHeader::decode(temp_data, temp_header) ||
547 validateFrameHeader(temp_header) != FrameReason::VALID) {
548 // Invalid header, skip this sync and look for next
549 buffer.clearArray(1);
550 progress = true;
551 available = buffer.available();
552 continue;
553 }
554
555 // Calculate frame length
556 int frame_len = temp_header.getFrameLength();
557 if (frame_len <= 0 || frame_len > buffer_size) { // Sanity check on frame size
558 // Invalid frame length, skip this sync
559 buffer.clearArray(1);
560 progress = true;
561 available = buffer.available();
562 continue;
563 }
564
565 // Check if we have complete frame
566 if (available < frame_len) {
567 break; // Need more data for complete frame
568 }
569
570 // Verify next frame sync if we have enough data
571 if (available >= frame_len + 2) {
572 if (seekFrameSync(temp_data + frame_len, 2) != 0) {
573 // No sync at expected position, this might not be a valid frame
574 buffer.clearArray(1);
575 progress = true;
576 available = buffer.available();
577 continue;
578 }
579 }
580
581 // We have a complete valid frame, write it to output
582 if (p_output != nullptr) {
583 size_t written = p_output->write(temp_data, frame_len);
584 if (written != frame_len) {
585 // Output error, we still need to remove the frame from buffer
586 LOGE("Failed to write complete frame");
587 }
588 }
589
590 // Update header for external access
591 last_frame_size = frame_len;
592 header = temp_header;
593 frame_header_valid = true;
594
595 // Remove processed frame from buffer
596 buffer.clearArray(frame_len);
597 available = buffer.available();
598
599 progress = true;
600 }
601
602 return progress;
603 }
604
605 bool validate(const uint8_t* data, size_t len) {
606 (void)data;
607 (void)len;
608 return FrameReason::VALID == validateFrameHeader(header);
609 }
610
611 bool contains(const uint8_t* data, const char* toFind, size_t len) {
612 if (data == nullptr || len == 0) return false;
613 int find_str_len = strlen(toFind);
614 for (int j = 0; j < len - find_str_len; j++) {
615 if (memcmp(data + j, toFind, find_str_len) == 0) return true;
616 }
617 return false;
618 }
619
620 // Seeks to the byte at the end of the next continuous run of 11 set bits.
621 //(ie. after seeking the cursor will be on the byte of which its 3 most
622 // significant bits are part of the frame sync)
623 int seekFrameSync(const uint8_t* str, size_t len) {
624 for (int j = 0; j < static_cast<int>(len) - 1; j++) {
625 // Look for 11-bit sync: 0xFFE? (0xFF followed by next byte with 0xE0 set)
626 if (str[j] == 0xFF && (str[j + 1] & 0xE0) == 0xE0) {
627 return j;
628 }
629 }
630 return -1;
631 }
632
633 void readFrameHeader(const uint8_t* data) {
634 if (!FrameHeader::decode(data, header)) return;
635 LOGI("- sample rate: %u", getSampleRate());
636 LOGI("- bit rate: %d", getBitRate());
637 }
638
639 enum class FrameReason {
640 VALID,
641 INVALID_BITRATE_FOR_VERSION,
642 INVALID_SAMPLERATE_FOR_VERSION,
643 INVALID_MPEG_VERSION,
644 INVALID_LAYER,
645 INVALID_LAYER_II_BITRATE_AND_MODE,
646 INVALID_EMPHASIS,
647 INVALID_CRC,
648 };
649
650 FrameReason validateFrameHeader(const FrameHeader& header) {
651 if (header.AudioVersion == FrameHeader::MPEGVersionID::INVALID) {
652 LOGI("invalid mpeg version");
653 return FrameReason::INVALID_MPEG_VERSION;
654 }
655
656 if (header.Layer == FrameHeader::LayerID::INVALID) {
657 LOGI("invalid layer");
658 return FrameReason::INVALID_LAYER;
659 }
660
661 if (header.getBitRate() <= 0) {
662 LOGI("invalid bitrate");
663 return FrameReason::INVALID_BITRATE_FOR_VERSION;
664 }
665
666 if (header.getSampleRate() == (unsigned short)FrameHeader::SpecialSampleRate::RESERVED) {
667 LOGI("invalid samplerate");
668 return FrameReason::INVALID_SAMPLERATE_FOR_VERSION;
669 }
670
671 // For Layer II there are some combinations of bitrate and mode which are
672 // not allowed
673 if (header.Layer == FrameHeader::LayerID::LAYER_2) {
674 if (header.ChannelMode == FrameHeader::ChannelModeID::SINGLE) {
675 if (header.getBitRate() >= 224000) {
676 LOGI("invalid bitrate >224000");
677 return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
678 }
679 } else {
680 if (header.getBitRate() >= 32000 && header.getBitRate() <= 56000) {
681 LOGI("invalid bitrate >32000");
682 return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
683 }
684
685 if (header.getBitRate() == 80000) {
686 LOGI("invalid bitrate >80000");
687 return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
688 }
689 }
690 }
691
692 if (header.Emphasis == FrameHeader::EmphasisID::INVALID) {
693 LOGI("invalid Emphasis");
694 return FrameReason::INVALID_EMPHASIS;
695 }
696
697 return FrameReason::VALID;
698 }
699};
700
701} // namespace audio_tools
MP3 header parser that processes MP3 data incrementally and extracts complete MP3 frames....
Definition HeaderParserMP3.h:25
int getChannels() const
Number of channels from mp3 header.
Definition HeaderParserMP3.h:416
size_t getPlayingTime(size_t fileSizeBytes)
Definition HeaderParserMP3.h:429
const char * getLayerStr() const
Provides a string representation of the MPEG layer.
Definition HeaderParserMP3.h:445
uint16_t getSampleRate() const
Sample rate from mp3 header.
Definition HeaderParserMP3.h:406
size_t getTimePerFrameMs()
playing time per frame in ms
Definition HeaderParserMP3.h:460
const char * getVersionStr() const
Provides a string representation of the MPEG version.
Definition HeaderParserMP3.h:436
bool hasValidFrame() const
Returns true if we have parsed at least one valid frame.
Definition HeaderParserMP3.h:479
int getFrameLength()
Frame length from mp3 header.
Definition HeaderParserMP3.h:423
HeaderParserMP3()=default
Default constructor.
size_t getFrameRateHz()
frame rate in Hz (frames per second)
Definition HeaderParserMP3.h:467
bool processBuffer()
Processes the internal buffer to extract complete mp3 frames.
Definition HeaderParserMP3.h:509
int findSyncWord(const uint8_t *buf, size_t nBytes, uint8_t synch=0xFF, uint8_t syncl=0xF0)
Finds the mp3/aac sync word.
Definition HeaderParserMP3.h:491
void reset()
Clears internal buffer and resets state.
Definition HeaderParserMP3.h:484
int getSamplesPerFrame()
number of samples per mp3 frame
Definition HeaderParserMP3.h:453
HeaderParserMP3(Print &output, int bufferSize=2048)
Constructor for write support.
Definition HeaderParserMP3.h:185
bool isValid(const uint8_t *data, int len)
parses the header string and returns true if this is a valid mp3 file
Definition HeaderParserMP3.h:216
size_t write(const uint8_t *data, size_t len)
split up the data into mp3 segements and write to output
Definition HeaderParserMP3.h:194
bool isValid()
Returns true if a valid frame has been detected.
Definition HeaderParserMP3.h:213
int getBitRate() const
Bit rate from mp3 header.
Definition HeaderParserMP3.h:411
Definition NoArduino.h:62
A simple Buffer implementation which just uses a (dynamically sized) array.
Definition Buffers.h:172
bool write(T sample) override
write add an entry to the buffer
Definition Buffers.h:205
int available() override
provides the number of entries that are available to read
Definition Buffers.h:232
bool isFull() override
checks if the buffer is full
Definition Buffers.h:239
bool resize(int size)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:304
T * data()
Provides address of actual data.
Definition Buffers.h:283
void reset() override
clears the buffer
Definition Buffers.h:285
int clearArray(int len) override
consumes len bytes and moves current data to the beginning
Definition Buffers.h:251
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10