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:
@@ -15,14 +15,11 @@
|
|||||||
|
|
||||||
/* see
|
/* see
|
||||||
* http://www.multiweb.cz/twoinches/MP3inside.htm
|
* 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]
|
// bit_rate_table[mpeg_version_index][layer_index][bitrate_index]
|
||||||
static const int bit_rate_table[4][4][16] =
|
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");
|
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);
|
//fDataSize = Source()->Seek(0, SEEK_END);
|
||||||
//if (sizeof(fRawHeader) != Source()->ReadAt(0, &fRawHeader, sizeof(fRawHeader))) {
|
//if (sizeof(fRawHeader) != Source()->ReadAt(0, &fRawHeader, sizeof(fRawHeader))) {
|
||||||
@@ -251,11 +253,181 @@ mp3Reader::GetNextChunk(void *cookie,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t
|
bool
|
||||||
mp3Reader::ID3Size(uint8 *buffer, size_t len)
|
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 */
|
if ((buffer[0] == 'I') && /* magic */
|
||||||
(buffer[1] == 'D') &&
|
(buffer[1] == 'D') &&
|
||||||
(buffer[2] == '3') &&
|
(buffer[2] == '3') &&
|
||||||
@@ -271,8 +443,7 @@ mp3Reader::ID3Size(uint8 *buffer, size_t len)
|
|||||||
bool
|
bool
|
||||||
mp3Reader::IsMp3File()
|
mp3Reader::IsMp3File()
|
||||||
{
|
{
|
||||||
// //! To detect an mp3 file, we seek into the middle,
|
// To detect an mp3 file, we seek into the middle,
|
||||||
// To detect an mp3 file, we first check for an ID3,
|
|
||||||
// and search for a valid sequence of 3 frame headers.
|
// and search for a valid sequence of 3 frame headers.
|
||||||
// A mp3 frame has a maximum length of 2881 bytes, we
|
// A mp3 frame has a maximum length of 2881 bytes, we
|
||||||
// load a block of 16kB and use it to search.
|
// load a block of 16kB and use it to search.
|
||||||
@@ -281,29 +452,7 @@ mp3Reader::IsMp3File()
|
|||||||
int64 offset;
|
int64 offset;
|
||||||
int32 size;
|
int32 size;
|
||||||
uint8 buf[search_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;
|
size = search_size;
|
||||||
offset = fFileSize / 2 - search_size / 2;
|
offset = fFileSize / 2 - search_size / 2;
|
||||||
if (size > fFileSize) {
|
if (size > fFileSize) {
|
||||||
@@ -317,29 +466,45 @@ mp3Reader::IsMp3File()
|
|||||||
TRACE("mp3ReaderPlugin::IsMp3File reading %ld bytes at offset %Ld failed\n", size, offset);
|
TRACE("mp3ReaderPlugin::IsMp3File reading %ld bytes at offset %Ld failed\n", size, offset);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
int32 end = size - 4;
|
||||||
for (int32 pos = 0; pos < (size - 8); pos++) {
|
for (int32 pos = 0; pos < end; pos++) {
|
||||||
int length1, length2, length3;
|
if (buf[pos] != 0xff) // quick check
|
||||||
|
|
||||||
//TRACE("---- checking for frame at %Ld\n", offset + pos);
|
|
||||||
length1 = GetFrameLength(&buf[pos]);
|
|
||||||
if (length1 < 0 || (pos + length1 + 4) > size)
|
|
||||||
continue;
|
continue;
|
||||||
TRACE(" found a valid frame header at file position %Ld\n", offset + pos);
|
if (IsValidStream(&buf[pos], size - pos))
|
||||||
length2 = GetFrameLength(&buf[pos + length1]);
|
return true;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
return false;
|
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
|
int
|
||||||
mp3Reader::GetFrameLength(void *header)
|
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",
|
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"),
|
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"),
|
layer_index == 3 ? "layer 1" : (layer_index == 2 ? "layer 2" : "layer 3"),
|
||||||
no_crc?"no":"has",
|
no_crc ? "no" : "has",
|
||||||
bitrate, samplingrate, padding, length);
|
bitrate, samplingrate, padding, length);
|
||||||
|
|
||||||
return length;
|
return length;
|
||||||
|
|||||||
@@ -32,9 +32,17 @@ public:
|
|||||||
BPositionIO *Source() { return fSeekableSource; }
|
BPositionIO *Source() { return fSeekableSource; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ssize_t ID3Size(uint8 *buffer, size_t len);
|
bool ParseFile();
|
||||||
bool IsMp3File();
|
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 GetFrameLength(void *header);
|
||||||
|
int GetXingVbrLength(uint8 *header);
|
||||||
|
int GetFraunhoferVbrLength(uint8 *header);
|
||||||
|
int GetLameVbrLength(uint8 *header);
|
||||||
|
int GetId3v2Length(uint8 *header);
|
||||||
|
|
||||||
bool FindData();
|
bool FindData();
|
||||||
|
|
||||||
@@ -42,6 +50,9 @@ private:
|
|||||||
private:
|
private:
|
||||||
BPositionIO * fSeekableSource;
|
BPositionIO * fSeekableSource;
|
||||||
int64 fFileSize;
|
int64 fFileSize;
|
||||||
|
|
||||||
|
int64 fDataStart;
|
||||||
|
int64 fDataSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user