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
This commit is contained in:
parent
ee727d5674
commit
a1862e53c5
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user