28 static const unsigned int SERIALIZED_SIZE = 4;
30 enum class MPEGVersionID :
unsigned {
37 enum class LayerID :
unsigned {
44 enum class ChannelModeID :
unsigned {
51 enum class EmphasisID :
unsigned {
58 enum SpecialBitrate { INVALID_BITRATE = -8000, ANY = 0 };
59 enum SpecialSampleRate { RESERVED = 0 };
62 MPEGVersionID AudioVersion = MPEGVersionID::INVALID;
63 LayerID Layer = LayerID::INVALID;
64 bool Protection =
false;
65 uint8_t BitrateIndex = 0;
66 uint8_t SampleRateIndex = 0;
69 ChannelModeID ChannelMode = ChannelModeID::STEREO;
70 uint8_t ExtentionMode = 0;
71 bool Copyright =
false;
72 bool Original =
false;
73 EmphasisID Emphasis = EmphasisID::NONE;
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;
84 out.AudioVersion =
static_cast<MPEGVersionID
>((b1 >> 3) & 0x03);
85 out.Layer =
static_cast<LayerID
>((b1 >> 1) & 0x03);
86 out.Protection = !(b1 & 0x01);
88 out.BitrateIndex = (b2 >> 4) & 0x0F;
89 out.SampleRateIndex = (b2 >> 2) & 0x03;
90 out.Padding = (b2 >> 1) & 0x01;
91 out.Private = (b2 & 0x01) != 0;
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);
101 signed int getBitRate()
const {
103 static const signed char rateTable[4][4][16] = {
107 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
109 {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, -1},
111 {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, -1},
113 {0, 4, 6, 7, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, -1},
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},
127 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
129 {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, -1},
131 {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, -1},
133 {0, 4, 6, 7, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, -1},
139 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
141 {0, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, -1},
143 {0, 4, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, -1},
145 {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, -1},
148 signed char rate_byte = rateTable[(int)AudioVersion][(
int)Layer][(int)BitrateIndex];
149 if (rate_byte == -1) {
150 LOGE(
"Unsupported bitrate");
153 return rate_byte * 8000;
156 unsigned short getSampleRate()
const {
158 static const unsigned short rateTable[4][4] = {
160 {11025, 12000, 8000, 0},
164 {22050, 24000, 16000, 0},
166 {44100, 48000, 32000, 0},
169 return rateTable[(int)AudioVersion][(
int)SampleRateIndex];
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));
187 void setOutput(
Print& output) {
191 void resize(
int size){buffer_size = size;}
194 size_t write(
const uint8_t* data,
size_t len) {
195 if (buffer.size() < buffer_size) buffer.
resize(buffer_size);
197 for (
int i = 0; i < len; i++) {
198 buffer.
write(data[i]);
208 if (p_output ==
nullptr)
return;
217 if (data ==
nullptr || len < 10) {
218 LOGE(
"Invalid input data or too small");
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;
226 const int MAX_SEARCH_DISTANCE = 8192;
229 if (len >= 10 && memcmp(data,
"ID3", 3) == 0) {
230 LOGI(
"ID3v2 tag found");
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);
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);
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)");
257 int current_pos = sync_pos;
258 FrameHeader first_header;
259 bool first_header_set =
false;
261 while (current_pos < len && (current_pos - sync_pos) < MAX_SEARCH_DISTANCE) {
262 int len_available = len - current_pos;
265 if (len_available < (
int)FrameHeader::SERIALIZED_SIZE) {
266 LOGD(
"Not enough data for header at position %d", current_pos);
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;
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;
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;
293 if (!first_header_set) {
294 first_header = temp_header;
295 first_header_set =
true;
296 header = temp_header;
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;
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) {
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;
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);
331 valid_frames_found++;
332 consecutive_frames++;
335 if (len_available < frame_len) {
336 LOGD(
"Incomplete frame at position %d (need %d, have %d)",
337 current_pos, frame_len, len_available);
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) {
346 current_pos = next_pos;
349 LOGD(
"No sync at expected position %d", next_pos);
350 consecutive_frames = 0;
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;
364 bool is_valid_mp3 =
false;
368 is_valid_mp3 = (consecutive_frames >= MIN_FRAMES_TO_VALIDATE);
369 }
else if (len >= 1024) {
371 is_valid_mp3 = (consecutive_frames >= 2) || (valid_frames_found >= MIN_FRAMES_TO_VALIDATE);
374 is_valid_mp3 = (valid_frames_found >= 1) && first_header_set;
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);
384 LOGI(
"Validation mode: STRICT (large buffer)");
385 }
else if (len >= 1024) {
386 LOGI(
"Validation mode: MODERATE (1KB+ buffer)");
388 LOGI(
"Validation mode: LENIENT (small buffer)");
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(
"-------------------");
398 LOGI(
"MP3 validation: INVALID (frames: %d, consecutive: %d, size: %d)",
399 valid_frames_found, consecutive_frames, len);
407 return frame_header_valid ? header.getSampleRate() : 0;
412 return frame_header_valid ? header.getBitRate() : 0;
417 if (!frame_header_valid)
return 0;
419 return (header.ChannelMode == FrameHeader::ChannelModeID::SINGLE) ? 1 : 2;
424 return frame_header_valid ? header.getFrameLength() : 0;
431 if (bitrate == 0)
return 0;
432 return fileSizeBytes / bitrate;
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
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"
454 if (header.Layer != FrameHeader::LayerID::LAYER_3)
return 0;
456 return header.AudioVersion == FrameHeader::MPEGVersionID::MPEG_1 ? 1152 : 576;
462 if (sample_rate == 0)
return 0;
469 if (time_per_frame == 0)
return 0;
470 return 1000 / time_per_frame;
474 FrameHeader getFrameHeader() {
475 return frame_header_valid ? header : FrameHeader{};
480 return frame_header_valid;
486 frame_header_valid =
false;
487 memset(&header, 0,
sizeof(header));
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)
502 Print* p_output =
nullptr;
504 bool frame_header_valid =
false;
505 size_t buffer_size = 0;
506 size_t last_frame_size = 0;
510 bool progress =
false;
513 while (available >= FrameHeader::SERIALIZED_SIZE) {
515 uint8_t* temp_data = buffer.
data();
518 int sync_pos = seekFrameSync(temp_data, available);
519 if (sync_pos == -1) {
521 size_t to_remove = (available > 3) ? available - 3 : 0;
540 if (available < FrameHeader::SERIALIZED_SIZE) {
545 FrameHeader temp_header;
546 if (!FrameHeader::decode(temp_data, temp_header) ||
547 validateFrameHeader(temp_header) != FrameReason::VALID) {
556 int frame_len = temp_header.getFrameLength();
557 if (frame_len <= 0 || frame_len > buffer_size) {
566 if (available < frame_len) {
571 if (available >= frame_len + 2) {
572 if (seekFrameSync(temp_data + frame_len, 2) != 0) {
582 if (p_output !=
nullptr) {
583 size_t written = p_output->write(temp_data, frame_len);
584 if (written != frame_len) {
586 LOGE(
"Failed to write complete frame");
591 last_frame_size = frame_len;
592 header = temp_header;
593 frame_header_valid =
true;
605 bool validate(
const uint8_t* data,
size_t len) {
608 return FrameReason::VALID == validateFrameHeader(header);
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;
623 int seekFrameSync(
const uint8_t* str,
size_t len) {
624 for (
int j = 0; j < static_cast<int>(len) - 1; j++) {
626 if (str[j] == 0xFF && (str[j + 1] & 0xE0) == 0xE0) {
633 void readFrameHeader(
const uint8_t* data) {
634 if (!FrameHeader::decode(data, header))
return;
639 enum class FrameReason {
641 INVALID_BITRATE_FOR_VERSION,
642 INVALID_SAMPLERATE_FOR_VERSION,
643 INVALID_MPEG_VERSION,
645 INVALID_LAYER_II_BITRATE_AND_MODE,
650 FrameReason validateFrameHeader(
const FrameHeader& header) {
651 if (header.AudioVersion == FrameHeader::MPEGVersionID::INVALID) {
652 LOGI(
"invalid mpeg version");
653 return FrameReason::INVALID_MPEG_VERSION;
656 if (header.Layer == FrameHeader::LayerID::INVALID) {
657 LOGI(
"invalid layer");
658 return FrameReason::INVALID_LAYER;
661 if (header.getBitRate() <= 0) {
662 LOGI(
"invalid bitrate");
663 return FrameReason::INVALID_BITRATE_FOR_VERSION;
666 if (header.getSampleRate() == (
unsigned short)FrameHeader::SpecialSampleRate::RESERVED) {
667 LOGI(
"invalid samplerate");
668 return FrameReason::INVALID_SAMPLERATE_FOR_VERSION;
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;
680 if (header.getBitRate() >= 32000 && header.getBitRate() <= 56000) {
681 LOGI(
"invalid bitrate >32000");
682 return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
685 if (header.getBitRate() == 80000) {
686 LOGI(
"invalid bitrate >80000");
687 return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
692 if (header.Emphasis == FrameHeader::EmphasisID::INVALID) {
693 LOGI(
"invalid Emphasis");
694 return FrameReason::INVALID_EMPHASIS;
697 return FrameReason::VALID;