From a1862e53c5624017343d292e1d3982c7f97f9189 Mon Sep 17 00:00:00 2001 From: beveloper Date: Sun, 9 Nov 2003 21:50:50 +0000 Subject: [PATCH] added more mp3 file parsing, VBR headers are now found, audio data position and size is determined git-svn-id: file:///srv/svn/repos/haiku/trunk/current@5293 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../plugins/mp3_reader/MP3ReaderPlugin.cpp | 271 ++++++++++++++---- .../plugins/mp3_reader/MP3ReaderPlugin.h | 13 +- 2 files changed, 230 insertions(+), 54 deletions(-) diff --git a/src/add-ons/media/plugins/mp3_reader/MP3ReaderPlugin.cpp b/src/add-ons/media/plugins/mp3_reader/MP3ReaderPlugin.cpp index 29d0a361ef..7eacd843f8 100644 --- a/src/add-ons/media/plugins/mp3_reader/MP3ReaderPlugin.cpp +++ b/src/add-ons/media/plugins/mp3_reader/MP3ReaderPlugin.cpp @@ -15,14 +15,11 @@ /* see * http://www.multiweb.cz/twoinches/MP3inside.htm - * + * http://www.id3.org/id3v2.3.0.txt + * http://www.id3.org/lyrics3.html + * http://www.id3.org/lyrics3200.html */ -/* minimal ID3 stuff - * see http://www.id3.org/id3v2.3.0.txt - */ -#define ID3_HEADER_SIZE 10 - // bit_rate_table[mpeg_version_index][layer_index][bitrate_index] static const int bit_rate_table[4][4][16] = { @@ -107,6 +104,11 @@ mp3Reader::Sniff(int32 *streamCount) TRACE("mp3Reader::Sniff: looks like an mp3 file\n"); + if (!ParseFile()) { + TRACE("mp3Reader::Sniff: parsing file failed\n"); + return B_ERROR; + } + //fDataSize = Source()->Seek(0, SEEK_END); //if (sizeof(fRawHeader) != Source()->ReadAt(0, &fRawHeader, sizeof(fRawHeader))) { @@ -251,11 +253,181 @@ mp3Reader::GetNextChunk(void *cookie, return result; } -ssize_t -mp3Reader::ID3Size(uint8 *buffer, size_t len) +bool +mp3Reader::ParseFile() +{ + // Since we already know that this is an mp3 file, + // detect the real (mp3 audio) data start and end + // and find VBR or other headers and tags + + const int32 search_size = 16384; + const int32 padding_size = 128; // Get???Length() functions need some bytes to look into + int64 offset; + int32 size; + uint8 buf[search_size]; + + fDataStart = -1; + + for (offset = 0; offset < fFileSize; ) { + int64 maxsize = fFileSize - offset; + size = (search_size < maxsize) ? search_size : maxsize; + + if (size != Source()->ReadAt(offset, buf, size)) { + TRACE("mp3ReaderPlugin::ParseFile reading %ld bytes at offset %Ld failed\n", size, offset); + return false; + } + + int skip_bytes = 0; + + // since the various Get???Length() functions need to check a few bytes + // (10 for ID3V2, about 40 for VBR), we stop searching before buffer end + int32 end = size - padding_size; + for (int32 pos = 0; pos < end; ) { + int hdr_length; + + // A Xing or Fraunhofer VBR header is embedded into a valid + // mp3 frame that contains silence. We need to first check + // for these headers before we can search for the start of a stream. + + hdr_length = GetXingVbrLength(&buf[pos]); + if (hdr_length > 0) { + TRACE("mp3ReaderPlugin::ParseFile found a Xing VBR header of %d bytes at position %Ld\n", hdr_length, offset + pos); + goto skip_header; + } + + hdr_length = GetFraunhoferVbrLength(&buf[pos]); + if (hdr_length > 0) { + TRACE("mp3ReaderPlugin::ParseFile found a Fraunhofer VBR header of %d bytes at position %Ld\n", hdr_length, offset + pos); + goto skip_header; + } + + hdr_length = GetLameVbrLength(&buf[pos]); + if (hdr_length > 0) { + TRACE("mp3ReaderPlugin::ParseFile found a Lame VBR header of %d bytes at position %Ld\n", hdr_length, offset + pos); + goto skip_header; + } + + hdr_length = GetId3v2Length(&buf[pos]); + if (hdr_length > 0) { + TRACE("mp3ReaderPlugin::ParseFile found a ID3V2 header of %d bytes at position %Ld\n", hdr_length, offset + pos); + goto skip_header; + } + + if (IsValidStream(&buf[pos], size - pos)) { + fDataStart = offset + pos; + break; + } + + pos++; + continue; + + skip_header: + int skip_max = end - pos; + skip_bytes = (skip_max < hdr_length) ? skip_max : hdr_length; + pos += skip_bytes; + skip_bytes = hdr_length - skip_bytes; + } + if (fDataStart != -1) + break; + + if (skip_bytes) { + offset += skip_bytes; + skip_bytes = 0; + } else { + offset += (search_size - padding_size); + } + } + + fDataSize = fFileSize - fDataStart; + + TRACE("found mp3 audio data at file position %Ld, maximum data length is %Ld\n", fDataStart, fDataSize); + + // search for a ID3 V1 tag + offset = fFileSize - 128; + size = 128; + if (offset > 0) { + if (size != Source()->ReadAt(offset, buf, size)) { + TRACE("mp3ReaderPlugin::ParseFile reading %ld bytes at offset %Ld failed\n", size, offset); + return false; + } + if (buf[0] == 'T'&& buf[1] == 'A' && buf[2] == 'G') { + TRACE("mp3ReaderPlugin::ParseFile found a ID3V1 header of 128 bytes at position %Ld\n", offset); + fDataSize -= 128; + } + } + + // search for a lyrics tag + // maximum size is 5100 bytes, and a 128 byte ID3V1 tag is always appended + // starts with "LYRICSBEGIN", end with "LYRICS200" or "LYRICSEND" + offset = fFileSize - 5300; + size = 5300; + if (offset < 0) { + offset = 0; + size = fFileSize; + } + if (size != Source()->ReadAt(offset, buf, size)) { + TRACE("mp3ReaderPlugin::ParseFile reading %ld bytes at offset %Ld failed\n", size, offset); + return false; + } + for (int pos = 0; pos < size; pos++) { + if (buf[pos] != 'L') + continue; + if (0 == memcmp(&buf[pos], "LYRICSBEGIN", 11)) { + TRACE("mp3ReaderPlugin::ParseFile found a Lyrics header at position %Ld\n", offset + pos); + fDataSize = fDataStart - offset + pos; + } + } + + // might search for APE tags, too + + TRACE("found mp3 audio data at file position %Ld, data length is %Ld\n", fDataStart, fDataSize); + + return true; +} + +int +mp3Reader::GetXingVbrLength(uint8 *header) +{ + int h_id = (header[1] >> 3) & 1; + int h_mode = (header[3] >> 6) & 3; + uint8 *xing_header; + + // determine offset of header + if(h_id) // mpeg1 + xing_header = (h_mode != 3) ? (header + 36) : (header + 21); + else // mpeg2 + xing_header = (h_mode != 3) ? (header + 21) : (header + 13); + + // note: in CBR files, there is an 'Info' tag of the same format + if (xing_header[0] != 'X') return -1; + if (xing_header[1] != 'i') return -1; + if (xing_header[2] != 'n') return -1; + if (xing_header[3] != 'g') return -1; + + return GetFrameLength(header); +} + +int +mp3Reader::GetLameVbrLength(uint8 *header) +{ + return -1; +} + +int +mp3Reader::GetFraunhoferVbrLength(uint8 *header) +{ + if (header[0] != 0xff) return -1; + if (header[36] != 'V') return -1; + if (header[37] != 'B') return -1; + if (header[38] != 'R') return -1; + if (header[39] != 'I') return -1; + + return GetFrameLength(header); +} + +int +mp3Reader::GetId3v2Length(uint8 *buffer) { - if (len < ID3_HEADER_SIZE) - return B_BAD_VALUE; if ((buffer[0] == 'I') && /* magic */ (buffer[1] == 'D') && (buffer[2] == '3') && @@ -271,8 +443,7 @@ mp3Reader::ID3Size(uint8 *buffer, size_t len) bool mp3Reader::IsMp3File() { - // //! To detect an mp3 file, we seek into the middle, - // To detect an mp3 file, we first check for an ID3, + // To detect an mp3 file, we seek into the middle, // and search for a valid sequence of 3 frame headers. // A mp3 frame has a maximum length of 2881 bytes, we // load a block of 16kB and use it to search. @@ -281,29 +452,7 @@ mp3Reader::IsMp3File() int64 offset; int32 size; uint8 buf[search_size]; - ssize_t id3size; - offset = 0; - size = (search_size > fFileSize)?fFileSize:search_size; - TRACE("searching for mp3 frame headers or ID3 at %Ld in %ld bytes\n", offset, size); - - if (size != Source()->ReadAt(offset, buf, size)) { - TRACE("mp3ReaderPlugin::IsMp3File reading %ld bytes at offset %Ld failed\n", size, offset); - return false; - } - id3size = ID3Size(buf, size); - if (id3size > 0) { - TRACE("found ID3, size %ld, skipping...\n", id3size); - offset += id3size; - size = (search_size+offset > fFileSize)?(fFileSize-offset):(search_size); - TRACE("searching for mp3 frame headers or ID3 at %Ld in %ld bytes\n", offset, size); - - if (size != Source()->ReadAt(offset, buf, size)) { - TRACE("mp3ReaderPlugin::IsMp3File reading %ld bytes at offset %Ld failed\n", size, offset); - return false; - } - } -#if 0 size = search_size; offset = fFileSize / 2 - search_size / 2; if (size > fFileSize) { @@ -317,29 +466,45 @@ mp3Reader::IsMp3File() TRACE("mp3ReaderPlugin::IsMp3File reading %ld bytes at offset %Ld failed\n", size, offset); return false; } -#endif - - for (int32 pos = 0; pos < (size - 8); pos++) { - int length1, length2, length3; - - //TRACE("---- checking for frame at %Ld\n", offset + pos); - length1 = GetFrameLength(&buf[pos]); - if (length1 < 0 || (pos + length1 + 4) > size) + + int32 end = size - 4; + for (int32 pos = 0; pos < end; pos++) { + if (buf[pos] != 0xff) // quick check continue; - TRACE(" found a valid frame header at file position %Ld\n", offset + pos); - length2 = GetFrameLength(&buf[pos + length1]); - if (length2 < 0 || (pos + length1 + length2 + 4) > size) - continue; - TRACE("## found second valid frame header at file position %Ld\n", offset + pos + length1); - length3 = GetFrameLength(&buf[pos + length1 + length2]); - if (length3 < 0) - continue; - TRACE("### found third valid frame header at file position %Ld\n", offset + pos + length1 + length2); - return true; + if (IsValidStream(&buf[pos], size - pos)) + return true; } return false; } +bool +mp3Reader::IsValidStream(uint8 *buffer, int size) +{ + // check 3 consecutive frame headers to make sure + // that the length encoded in the header is correct, + // and also that mpeg version and layer do not change + int length1 = GetFrameLength(buffer); + if (length1 < 0 || (length1 + 4) > size) + return false; + int version_index1 = (buffer[1] >> 3) & 0x03; + int layer_index1 = (buffer[1] >> 1) & 0x03; + int length2 = GetFrameLength(buffer + length1); + if (length2 < 0 || (length1 + length2 + 4) > size) + return false; + int version_index2 = (buffer[length1 + 1] >> 3) & 0x03; + int layer_index2 = (buffer[length1 + 1] >> 1) & 0x03; + if (version_index1 != version_index2 || layer_index1 != layer_index1) + return false; + int length3 = GetFrameLength(buffer + length1 + length2); + if (length3 < 0) + return false; + int version_index3 = (buffer[length1 + length2 + 1] >> 3) & 0x03; + int layer_index3 = (buffer[length1 + length2 + 1] >> 1) & 0x03; + if (version_index2 != version_index3 || layer_index2 != layer_index3) + return false; + return true; +} + int mp3Reader::GetFrameLength(void *header) { @@ -376,7 +541,7 @@ mp3Reader::GetFrameLength(void *header) TRACE("%s %s, %s crc, bit rate %d, sampling rate %d, padding %d, frame length %d\n", mpeg_version_index == 0 ? "mpeg 2.5" : (mpeg_version_index == 2 ? "mpeg 2" : "mpeg 1"), layer_index == 3 ? "layer 1" : (layer_index == 2 ? "layer 2" : "layer 3"), - no_crc?"no":"has", + no_crc ? "no" : "has", bitrate, samplingrate, padding, length); return length; diff --git a/src/add-ons/media/plugins/mp3_reader/MP3ReaderPlugin.h b/src/add-ons/media/plugins/mp3_reader/MP3ReaderPlugin.h index d2f2878de8..57f018460e 100644 --- a/src/add-ons/media/plugins/mp3_reader/MP3ReaderPlugin.h +++ b/src/add-ons/media/plugins/mp3_reader/MP3ReaderPlugin.h @@ -32,9 +32,17 @@ public: BPositionIO *Source() { return fSeekableSource; } private: - ssize_t ID3Size(uint8 *buffer, size_t len); + bool ParseFile(); bool IsMp3File(); + + // checks if the buffer contains a valid mp3 stream, length should be + bool IsValidStream(uint8 *buffer, int size); + int GetFrameLength(void *header); + int GetXingVbrLength(uint8 *header); + int GetFraunhoferVbrLength(uint8 *header); + int GetLameVbrLength(uint8 *header); + int GetId3v2Length(uint8 *header); bool FindData(); @@ -42,6 +50,9 @@ private: private: BPositionIO * fSeekableSource; int64 fFileSize; + + int64 fDataStart; + int64 fDataSize; };