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