/* daapd mp3 parser (c) deleet 2002-2003, Johannes Zander & Alexander Oberdšrster daapd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. daapd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with daapd; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include "types.h" #include "parsemp3.h" using namespace std; ////// // FileMapR class FileMapR { public: FileMapR( const char *fileName ) : fDesc(0), mem(0), fileSize(0) { fDesc = open( fileName, O_RDONLY, 0 ); struct stat sb; stat( fileName, &sb ); fileSize = sb.st_size; mem = mmap( NULL, fileSize, PROT_READ, MAP_SHARED, fDesc, 0 ); close(fDesc); } ~FileMapR() { #ifdef __sun__ if( mem ) munmap( (char *) mem, fileSize ); #else if( mem ) munmap( mem, fileSize ); #endif } inline u8* data() { return( (u8*)mem ); } inline u32 size() { return( fileSize ); } private: int fDesc; void* mem; u32 fileSize; }; ////// // ID3 struct ID3v1Header { char tag[3]; char title[30]; char artist[30]; char album[30]; char year[4]; char comment[29]; u8 track; u8 genre; } __attribute(( packed )); struct ID3v2Header { char tag[3]; u8 versionHi, versionLo; u8 flags; u8 size[4]; } __attribute(( packed )); u32 parseID3v1( const u8* src, u32 size ) { // return stream size without id3v1 header if( size>sizeof( ID3v1Header )) { ID3v1Header* id = (ID3v1Header*)( src+size-sizeof( ID3v1Header )); // ...at the end of the stream if( id->tag[0]=='T' && id->tag[1]=='A' && id->tag[2]=='G' ) { // bingo! return( size-sizeof( ID3v1Header )); // strip tag from the end } } return( size ); // ...no id3v1 tag present } u32 parseID3v2( const u8* src, u32 size ) { // return start of mpeg stream if( size>sizeof( ID3v2Header )) { ID3v2Header* id = (ID3v2Header*)src; // should be at the beginning of the stream if( id->tag[0]=='I' && id->tag[1]=='D' && id->tag[2]=='3' ) { // bingo! u32 pos = sizeof( ID3v2Header ); const u32 idSize = id->size[3] + ( id->size[2]<<7 ) + ( id->size[1]<<14 ) + ( id->size[0]<<21 ); const u32 idEnd = pos + idSize; return( idEnd ); } } return( 0 ); // ...no id3v2 tag present } ////// // Xing/LAME struct XingHeader { char tag[4]; u32 flags; u32 numFrames; u32 numBytes; // etc, but we're only interested in the first two fields } __attribute(( packed )); s32 parseXing( const u8* src, const u32 size, u32 &numFrames, u32 &numBytes ) { if( size>sizeof( XingHeader )) { XingHeader* xing = (XingHeader*)src; if(( xing->tag[0]=='X' && xing->tag[1]=='i' && xing->tag[2]=='n' && xing->tag[3]=='g') || ( xing->tag[0]=='I' && xing->tag[1]=='n' && xing->tag[2]=='f' && xing->tag[3]=='o')) { if( (xing->flags&1) && ( xing->flags >> 1)&1) { numFrames = xing->numFrames; numBytes = xing->numBytes; return 0; } else return -1; } } return -1; } void calcValues( const u8 layer, const u8 mpegVersion, const u32 sampleRate, u32 numFrames, u32 numBytes, double &bitrate, double &time ) { double tpf; if( layer == 1 ) tpf = 384.0 / sampleRate; else tpf = 1152.0 / sampleRate; if( (mpegVersion == 2) || (mpegVersion == 3) ) tpf /= 2; time = ( tpf * numFrames * 1000.0 ); bitrate = (numBytes * 8.0) / (tpf * numFrames * 1000.0) ; } ////// // MP3 const u32 mp3VersionLut[] = { 3,0,2,1 }; const char* mp3VersionStr[] = { "reserved", "MPEG 1", "MPEG 2", "MPEG 2.5" }; const u32 mp3LayerLut[] = { 0, 3, 2, 1 }; const char* mp3LayerStr[] = { "reserved", "Layer I", "Layer II", "Layer III" }; const u32 mp3BitrateLut[2][3] = {{0,1,2}, {3,4,4}}; const s32 mp3BitrateIdx[][5] = { { 0, 0, 0, 0, 0 }, { 32, 32, 32, 32, 8 }, { 64, 48, 40, 48, 16 }, { 96, 56, 48, 56, 24 }, { 128, 64, 56, 64, 32 }, { 160, 80, 64, 80, 40 }, { 192, 96, 80, 96, 48 }, { 224, 112, 96, 112, 56 }, { 256, 128, 112, 128, 64 }, { 288, 160, 128, 144, 80 }, { 320, 192, 160, 160, 96 }, { 352, 224, 192, 176, 112 }, { 384, 256, 224, 192, 128 }, { 416, 320, 256, 224, 144 }, { 448, 384, 320, 256, 160 }, { -1, -1, -1, -1, -1 } }; const s32 mp3FreqIdx[][3] = { { 44100, 22050, 11025 }, { 48000, 24000, 12000 }, { 32000, 16000, 8000 } }; const char* mp3ChannelModeStr[] = { "Stereo", "Joint stereo (Stereo)", "Dual channel (Stereo)", "Single channel (Mono)" }; const char* mp3ModeExtStr[][2] = { { "bands 4 to 31", "-" }, { "bands 8 to 31", "Intensity stereo" }, { "bands 12 to 31", "MS stereo" }, { "bands 16 to 31", "Intensity stereo+MS stereo" }, }; const char* mp3EmphasisStr[] = { "none", "50/15 ms", "reserved", "CCIT J.17" }; void parseMp3Frames( u8* src, u32 skip, u32 size, u32 maxParse, Mp3Info& out ) { const u8* end = src+size-3; // header consists of 4 bytes u8* pos = src+skip; bool cbr = true; s32 lastSampleRate; s32 lastBitRate; u32 frameSize; #ifdef __APPLE__ u32 purgecounter = 0; u8* lastaddress = src; #endif if( maxParse == 0 ) cbr = false; while( pos= 1000) { msync( lastaddress, pos-lastaddress, MS_INVALIDATE ); purgecounter = 0; lastaddress = pos; } #endif if(( header & 0xe000 )==0xe000 ) { // bingo! const u8 version = mp3VersionLut[(header >> 11 )&3]; const u8 layer = mp3LayerLut[(header >> 9 )&3]; const u8 bitRateIdx = (header >> 20 )&15; const u8 sampleRateIdx = (header >> 18 )&3; const u8 channelModeIdx = (header >> 15 )&3; if( version==0 || layer==0 || bitRateIdx==0 || bitRateIdx==0xf || sampleRateIdx==0x3 ) { // ugh, that's wrong! // bad mp3 sync found, lost sync ++pos; continue; } const s32 bitRate = mp3BitrateIdx[bitRateIdx][mp3BitrateLut[version>=2][layer-1]]; const s32 sampleRate = mp3FreqIdx[sampleRateIdx][version-1]; const bool padding = (header>>17)&1; frameSize = layer==1 ? ( version==1 ? 48000 : 24000 )*bitRate/sampleRate + ( padding ? 4 : 0 ) : ( version==1 ? 144000 : 72000 )*bitRate/sampleRate + ( padding ? 1 : 0 ); if( out.frameCount == 0 ) { // first Frame u32 numFrames, numBytes; u8 *xingPos = pos + ( version == 1 ? ( channelModeIdx == 3 ? 21 : 36 ) // mono = 3 : ( channelModeIdx == 3 ? 23 : 21 )); if( parseXing( xingPos, size, numFrames, numBytes ) >= 0 ) { calcValues( layer, version, sampleRate, numFrames, numBytes, out.bitRate, out.time ); out.sampleRate = sampleRate; return; } } out.frameCount++; if( out.frameCount <= maxParse ) { if( out.frameCount > 1 ) { cbr = cbr && ( lastSampleRate == sampleRate ); cbr = cbr && ( lastBitRate == bitRate ); } lastSampleRate = sampleRate; lastBitRate = bitRate; } out.sampleRate += (double) sampleRate; out.bitRate += (double) bitRate; pos += frameSize; // jump to next header continue; } } // out of sync ++pos; } #ifdef __APPLE__ msync( lastaddress, pos-lastaddress, MS_INVALIDATE ); #endif out.bitRate /= (double) out.frameCount; out.sampleRate /= (double) out.frameCount; // very strange -- the bitrate seems to include all headers and tags, // so subtracting them from the file size is wrong. // // out.time = (double) ( size - skip )/( out.bitRate/8.0 ); out.time = (double) ( size )/( out.bitRate/8.0 ); // out.time = (double) out.frameCount * (26.0 * 1.00473); } bool parseMp3( const char *filename, u32 maxParse, Mp3Info& outHeader ) { FileMapR mapping( filename ); u8* src = mapping.data(); u32 size = mapping.size(); if( src && size>0 && size!=0xffffffff ) { u32 streamEnd = parseID3v1( src, size ); u32 streamStart = parseID3v2( src, streamEnd ); parseMp3Frames( src, streamStart, streamEnd-streamStart, maxParse, outHeader ); return( true ); } else return( false ); }