| // -*- C++ -*- |
| // $Id: mp3_parse.cpp,v 1.6 2002/11/02 17:48:51 t1mpy Exp $ |
| |
| // id3lib: a C++ library for creating and manipulating id3v1/v2 tags |
| // Copyright 2002, Thijmen Klok (thijmen@id3lib.org) |
| |
| // This library is free software; you can redistribute it and/or modify it |
| // under the terms of the GNU Library General Public License as published by |
| // the Free Software Foundation; either version 2 of the License, or (at your |
| // option) any later version. |
| // |
| // This library 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 Library General Public |
| // License for more details. |
| // |
| // You should have received a copy of the GNU Library General Public License |
| // along with this library; if not, write to the Free Software Foundation, |
| // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| |
| // The id3lib authors encourage improvements and optimisations to be sent to |
| // the id3lib coordinator. Please see the README file for details on where to |
| // send such submissions. See the AUTHORS file for a list of people who have |
| // contributed to id3lib. See the ChangeLog file for a list of changes to |
| // id3lib. These files are distributed with id3lib at |
| // http://download.sourceforge.net/id3lib/ |
| |
| #include "mp3_header.h" |
| |
| #define FRAMES_FLAG 0x0001 |
| #define BYTES_FLAG 0x0002 |
| #define TOC_FLAG 0x0004 |
| #define SCALE_FLAG 0x0008 |
| |
| static int ExtractI4(unsigned char *buf) |
| { |
| int x; |
| // big endian extract |
| |
| x = buf[0]; |
| x <<= 8; |
| x |= buf[1]; |
| x <<= 8; |
| x |= buf[2]; |
| x <<= 8; |
| x |= buf[3]; |
| |
| return x; |
| } |
| |
| uint32 fto_nearest_i(float f) |
| { |
| uint32 i; |
| |
| i = (uint32)f; |
| if (i < f) |
| { |
| f -= i; |
| if (f >= 0.5) |
| return i+1; |
| else |
| return i; |
| } |
| else |
| return i; |
| } |
| |
| uint16 calcCRC(char *pFrame, size_t audiodatasize) |
| { |
| size_t icounter; |
| int tmpchar, crcmask, tmpi; |
| uint16 crc = 0xffff; |
| |
| for (icounter = 2; icounter < audiodatasize; ++icounter) |
| { |
| if (icounter != 4 && icounter != 5) //skip the 2 chars of the crc itself |
| { |
| crcmask = 1 << 8; |
| tmpchar = pFrame[icounter]; |
| while (crcmask >>= 1) |
| { |
| tmpi = crc & 0x8000; |
| crc <<= 1; |
| if (!tmpi ^ !(tmpchar & crcmask)) |
| crc ^= 0x8005; |
| } |
| } |
| } |
| crc &= 0xffff; |
| return crc; |
| } |
| |
| void Mp3Info::Clean() |
| { |
| if (_mp3_header_output != NULL) |
| delete _mp3_header_output; |
| _mp3_header_output = NULL; |
| } |
| |
| using namespace dami; |
| |
| bool Mp3Info::Parse(ID3_Reader& reader, size_t mp3size) |
| { |
| MP3_BitRates _mp3_bitrates[2][3][16] = |
| { |
| { |
| { //MPEG 1, LAYER I |
| MP3BITRATE_NONE, |
| MP3BITRATE_32K, |
| MP3BITRATE_64K, |
| MP3BITRATE_96K, |
| MP3BITRATE_128K, |
| MP3BITRATE_160K, |
| MP3BITRATE_192K, |
| MP3BITRATE_224K, |
| MP3BITRATE_256K, |
| MP3BITRATE_288K, |
| MP3BITRATE_320K, |
| MP3BITRATE_352K, |
| MP3BITRATE_384K, |
| MP3BITRATE_416K, |
| MP3BITRATE_448K, |
| MP3BITRATE_FALSE |
| }, |
| { //MPEG 1, LAYER II |
| MP3BITRATE_NONE, |
| MP3BITRATE_32K, |
| MP3BITRATE_48K, |
| MP3BITRATE_56K, |
| MP3BITRATE_64K, |
| MP3BITRATE_80K, |
| MP3BITRATE_96K, |
| MP3BITRATE_112K, |
| MP3BITRATE_128K, |
| MP3BITRATE_160K, |
| MP3BITRATE_192K, |
| MP3BITRATE_224K, |
| MP3BITRATE_256K, |
| MP3BITRATE_320K, |
| MP3BITRATE_384K, |
| MP3BITRATE_FALSE |
| }, |
| { //MPEG 1, LAYER III |
| MP3BITRATE_NONE, |
| MP3BITRATE_32K, |
| MP3BITRATE_40K, |
| MP3BITRATE_48K, |
| MP3BITRATE_56K, |
| MP3BITRATE_64K, |
| MP3BITRATE_80K, |
| MP3BITRATE_96K, |
| MP3BITRATE_112K, |
| MP3BITRATE_128K, |
| MP3BITRATE_160K, |
| MP3BITRATE_192K, |
| MP3BITRATE_224K, |
| MP3BITRATE_256K, |
| MP3BITRATE_320K, |
| MP3BITRATE_FALSE |
| } |
| }, |
| { |
| { //MPEG 2 or 2.5, LAYER I |
| MP3BITRATE_NONE, |
| MP3BITRATE_32K, |
| MP3BITRATE_48K, |
| MP3BITRATE_56K, |
| MP3BITRATE_64K, |
| MP3BITRATE_80K, |
| MP3BITRATE_96K, |
| MP3BITRATE_112K, |
| MP3BITRATE_128K, |
| MP3BITRATE_144K, |
| MP3BITRATE_160K, |
| MP3BITRATE_176K, |
| MP3BITRATE_192K, |
| MP3BITRATE_224K, |
| MP3BITRATE_256K, |
| MP3BITRATE_FALSE |
| }, |
| { //MPEG 2 or 2.5, LAYER II |
| MP3BITRATE_NONE, |
| MP3BITRATE_8K, |
| MP3BITRATE_16K, |
| MP3BITRATE_24K, |
| MP3BITRATE_32K, |
| MP3BITRATE_40K, |
| MP3BITRATE_48K, |
| MP3BITRATE_56K, |
| MP3BITRATE_64K, |
| MP3BITRATE_80K, |
| MP3BITRATE_96K, |
| MP3BITRATE_112K, |
| MP3BITRATE_128K, |
| MP3BITRATE_144K, |
| MP3BITRATE_160K, |
| MP3BITRATE_FALSE |
| }, |
| { //MPEG 2 or 2.5, LAYER III |
| MP3BITRATE_NONE, |
| MP3BITRATE_8K, |
| MP3BITRATE_16K, |
| MP3BITRATE_24K, |
| MP3BITRATE_32K, |
| MP3BITRATE_40K, |
| MP3BITRATE_48K, |
| MP3BITRATE_56K, |
| MP3BITRATE_64K, |
| MP3BITRATE_80K, |
| MP3BITRATE_96K, |
| MP3BITRATE_112K, |
| MP3BITRATE_128K, |
| MP3BITRATE_144K, |
| MP3BITRATE_160K, |
| MP3BITRATE_FALSE |
| } |
| } |
| }; |
| |
| Mp3_Frequencies _mp3_frequencies[4][4] = |
| { |
| { MP3FREQUENCIES_11025HZ, MP3FREQUENCIES_12000HZ, MP3FREQUENCIES_8000HZ,MP3FREQUENCIES_Reserved }, //MPEGVERSION_2_5 |
| { MP3FREQUENCIES_Reserved, MP3FREQUENCIES_Reserved, MP3FREQUENCIES_Reserved, MP3FREQUENCIES_Reserved}, //MPEGVERSION_Reserved |
| { MP3FREQUENCIES_22050HZ, MP3FREQUENCIES_24000HZ, MP3FREQUENCIES_16000HZ, MP3FREQUENCIES_Reserved }, //MPEGVERSION_2 |
| { MP3FREQUENCIES_44100HZ, MP3FREQUENCIES_48000HZ, MP3FREQUENCIES_32000HZ, MP3FREQUENCIES_Reserved } //MPEGVERSION_1 |
| }; |
| |
| _mp3_header_internal *_tmpheader; |
| |
| const size_t HEADERSIZE = 4;// |
| char buf[HEADERSIZE+1]; //+1 to hold the \0 char |
| ID3_Reader::pos_type beg = reader.getCur() ; |
| ID3_Reader::pos_type end = beg + HEADERSIZE ; |
| reader.setCur(beg); |
| int bitrate_index; |
| |
| _mp3_header_output->layer = MPEGLAYER_FALSE; |
| _mp3_header_output->version = MPEGVERSION_FALSE; |
| _mp3_header_output->bitrate = MP3BITRATE_FALSE; |
| _mp3_header_output->channelmode = MP3CHANNELMODE_FALSE; |
| _mp3_header_output->modeext = MP3MODEEXT_FALSE; |
| _mp3_header_output->emphasis = MP3EMPHASIS_FALSE; |
| _mp3_header_output->crc = MP3CRC_MISMATCH; |
| _mp3_header_output->frequency = 0; |
| _mp3_header_output->framesize = 0; |
| _mp3_header_output->frames = 0; |
| _mp3_header_output->time = 0; |
| _mp3_header_output->vbr_bitrate = 0; |
| |
| reader.readChars(buf, HEADERSIZE); |
| buf[HEADERSIZE]='\0'; |
| // copy the pointer to the struct |
| |
| if (((buf[0] & 0xFF) != 0xFF) || ((buf[1] & 0xE0) != 0xE0)) //first 11 bits should be 1 |
| { |
| this->Clean(); |
| return false; |
| } |
| |
| _tmpheader = reinterpret_cast<_mp3_header_internal *>(buf); |
| |
| bitrate_index = 0; |
| switch (_tmpheader->id) |
| { |
| case 3: |
| _mp3_header_output->version = MPEGVERSION_1; |
| bitrate_index = 0; |
| break; |
| case 2: |
| _mp3_header_output->version = MPEGVERSION_2; |
| bitrate_index = 1; |
| break; |
| case 1: |
| this->Clean(); |
| return false; //wouldn't know how to handle it |
| break; |
| case 0: |
| _mp3_header_output->version = MPEGVERSION_2_5; |
| bitrate_index = 1; |
| break; |
| default: |
| this->Clean(); |
| return false; |
| break; |
| }; |
| |
| switch (_tmpheader->layer) |
| { |
| case 3: |
| _mp3_header_output->layer = MPEGLAYER_I; |
| break; |
| case 2: |
| _mp3_header_output->layer = MPEGLAYER_II; |
| break; |
| case 1: |
| _mp3_header_output->layer = MPEGLAYER_III; |
| break; |
| case 0: |
| this->Clean(); |
| return false; //wouldn't know how to handle it |
| break; |
| default: |
| this->Clean(); |
| return false; //how can two unsigned bits be something else?? |
| break; |
| }; |
| |
| // mpegversion, layer and bitrate are all valid |
| _mp3_header_output->bitrate = _mp3_bitrates[bitrate_index][3-_tmpheader->layer][_tmpheader->bitrate_index]; |
| if (_mp3_header_output->bitrate == MP3BITRATE_FALSE) |
| { |
| this->Clean(); |
| return false; |
| } |
| _mp3_header_output->frequency = _mp3_frequencies[_tmpheader->id][_tmpheader->frequency]; |
| if (_mp3_header_output->frequency == MP3FREQUENCIES_Reserved) |
| { |
| this->Clean(); |
| return false; |
| } |
| |
| _mp3_header_output->privatebit = (bool)_tmpheader->private_bit; |
| _mp3_header_output->copyrighted = (bool)_tmpheader->copyright; |
| _mp3_header_output->original = (bool)_tmpheader->original; |
| _mp3_header_output->crc = (Mp3_Crc)!(bool)_tmpheader->protection_bit; |
| |
| switch (_tmpheader->mode) |
| { |
| case 3: |
| _mp3_header_output->channelmode = MP3CHANNELMODE_SINGLE_CHANNEL; |
| break; |
| case 2: |
| _mp3_header_output->channelmode = MP3CHANNELMODE_DUAL_CHANNEL; |
| break; |
| case 1: |
| _mp3_header_output->channelmode = MP3CHANNELMODE_JOINT_STEREO; |
| break; |
| case 0: |
| _mp3_header_output->channelmode = MP3CHANNELMODE_STEREO; |
| break; |
| default: |
| this->Clean(); |
| return false; //wouldn't know how to handle it |
| break; |
| } |
| |
| if (_mp3_header_output->channelmode == MP3CHANNELMODE_JOINT_STEREO) |
| { |
| // these have a different meaning for different layers, better give them a generic name in the enum |
| switch (_tmpheader->mode_ext) |
| { |
| case 3: |
| _mp3_header_output->modeext = MP3MODEEXT_3; |
| break; |
| case 2: |
| _mp3_header_output->modeext = MP3MODEEXT_2; |
| break; |
| case 1: |
| _mp3_header_output->modeext = MP3MODEEXT_1; |
| break; |
| case 0: |
| _mp3_header_output->modeext = MP3MODEEXT_0; |
| break; |
| default: |
| this->Clean(); |
| return false; //wouldn't know how to handle it |
| break; |
| } |
| } |
| else //it's valid to have a valid false one in this case, since it's only used with joint stereo |
| _mp3_header_output->modeext = MP3MODEEXT_FALSE; |
| |
| switch (_tmpheader->emphasis) |
| { |
| case 3: |
| _mp3_header_output->emphasis = MP3EMPHASIS_CCIT_J17; |
| break; |
| case 2: |
| _mp3_header_output->emphasis = MP3EMPHASIS_Reserved; |
| break; |
| case 1: |
| _mp3_header_output->emphasis = MP3EMPHASIS_50_15MS; |
| break; |
| case 0: |
| _mp3_header_output->emphasis = MP3EMPHASIS_NONE; |
| break; |
| default: |
| this->Clean(); |
| return false; //wouldn't know how to handle it |
| break; |
| } |
| |
| //http://www.mp3-tech.org/programmer/frame_header.html |
| if (_mp3_header_output->bitrate != MP3BITRATE_NONE && _mp3_header_output->frequency > 0) |
| { |
| |
| switch(_mp3_header_output->layer) |
| { |
| case MPEGLAYER_I: // Layer 1 |
| _mp3_header_output->framesize = 4 * (12 * _mp3_header_output->bitrate / _mp3_header_output->frequency + (_tmpheader->padding_bit ? 1 : 0)); |
| break; |
| case MPEGLAYER_II: // Layer 2 |
| _mp3_header_output->framesize = 144 * _mp3_header_output->bitrate / _mp3_header_output->frequency + (_tmpheader->padding_bit ? 1 : 0); |
| break; |
| case MPEGLAYER_III: // Layer 3 |
| // See http://www.hydrogenaudio.org/forums/lofiversion/index.php/t43172.html |
| // and http://minnie.tuhs.org/pipermail/mp3encoder/2003-February/005598.html |
| // on discussion about the frame size calculation. --DmitryD |
| if(_mp3_header_output->version == MPEGVERSION_1) |
| _mp3_header_output->framesize = 144 * _mp3_header_output->bitrate / _mp3_header_output->frequency + (_tmpheader->padding_bit ? 1 : 0); //Mpeg1 |
| else |
| _mp3_header_output->framesize = 72 * _mp3_header_output->bitrate / _mp3_header_output->frequency + (_tmpheader->padding_bit ? 1 : 0); //Mpeg2 + Mpeg2.5 |
| break; |
| } |
| // if (_mp3_header_output->layer == MPEGLAYER_I) |
| // _mp3_header_output->framesize = fto_nearest_i((float)((48 * (float)_mp3_header_output->bitrate) / _mp3_header_output->frequency)) + (_tmpheader->padding_bit ? 4 : 0); |
| // else |
| // _mp3_header_output->framesize = fto_nearest_i((float)((144 * (float)_mp3_header_output->bitrate) / _mp3_header_output->frequency)) + (_tmpheader->padding_bit ? 1 : 0); |
| } |
| else |
| _mp3_header_output->framesize = 0; //unable to determine |
| |
| const size_t CRCSIZE = 2; |
| size_t sideinfo_len; |
| |
| if (_mp3_header_output->version == MPEGVERSION_1) /* MPEG 1 */ |
| sideinfo_len = (_mp3_header_output->channelmode == MP3CHANNELMODE_SINGLE_CHANNEL) ? 4 + 17 : 4 + 32; |
| else /* MPEG 2 */ |
| sideinfo_len = (_mp3_header_output->channelmode == MP3CHANNELMODE_SINGLE_CHANNEL) ? 4 + 9 : 4 + 17; |
| |
| int vbr_header_offest = beg + sideinfo_len; |
| int vbr_frames = 0; |
| |
| sideinfo_len += 2; // add two for the crc itself |
| |
| if ((_mp3_header_output->crc == MP3CRC_OK) && mp3size < sideinfo_len) |
| _mp3_header_output->crc = MP3CRC_ERROR_SIZE; |
| |
| if (_mp3_header_output->crc == MP3CRC_OK) |
| { |
| char audiodata[38 + 1]; //+1 to hold the 0 char |
| uint16 crc16; |
| uint16 crcstored; |
| |
| _mp3_header_output->crc = MP3CRC_MISMATCH; //as a starting point, we assume the worst |
| |
| reader.setCur(beg); |
| |
| reader.readChars(audiodata, sideinfo_len); |
| audiodata[sideinfo_len] = '\0'; |
| |
| crc16 = calcCRC(audiodata, sideinfo_len); |
| |
| beg = end; |
| end = beg + CRCSIZE; |
| |
| reader.setCur(beg); |
| crcstored = (uint16)io::readBENumber(reader, CRCSIZE); |
| |
| // a mismatch doesn't mean the file is unusable |
| // it has just some bits in the wrong place |
| if (crcstored == crc16) |
| _mp3_header_output->crc = MP3CRC_OK; |
| } |
| |
| // read xing/vbr header if present |
| // derived from code in vbrheadersdk.zip |
| // from http://www.xingtech.com/developer/mp3/ |
| |
| const size_t VBR_HEADER_MIN_SIZE = 8; // "xing" + flags are fixed |
| const size_t VBR_HEADER_MAX_SIZE = 120; // frames, bytes, toc and scale are optional |
| |
| if (mp3size >= vbr_header_offest + VBR_HEADER_MIN_SIZE) |
| { |
| char vbrheaderdata[VBR_HEADER_MAX_SIZE+1]; //+1 to hold the 0 char |
| unsigned char *pvbrdata = (unsigned char *)vbrheaderdata; |
| int vbr_filesize = 0; |
| int vbr_scale = 0; |
| int vbr_flags = 0; |
| |
| // get fixed part of vbr header |
| // and check if valid |
| |
| beg = vbr_header_offest; |
| reader.setCur(beg); |
| reader.readChars(vbrheaderdata, VBR_HEADER_MIN_SIZE); |
| vbrheaderdata[VBR_HEADER_MIN_SIZE] = '\0'; |
| |
| if (pvbrdata[0] == 'X' && |
| pvbrdata[1] == 'i' && |
| pvbrdata[2] == 'n' && |
| pvbrdata[3] == 'g') |
| { |
| // get vbr flags |
| pvbrdata += 4; |
| vbr_flags = ExtractI4(pvbrdata); |
| pvbrdata += 4; |
| |
| // read entire vbr header |
| int vbr_header_size = VBR_HEADER_MIN_SIZE |
| + ((vbr_flags & FRAMES_FLAG)? 4:0) |
| + ((vbr_flags & BYTES_FLAG)? 4:0) |
| + ((vbr_flags & TOC_FLAG)? 100:0) |
| + ((vbr_flags & SCALE_FLAG)? 4:0); |
| |
| if (mp3size >= vbr_header_offest + vbr_header_size) |
| { |
| reader.readChars(&vbrheaderdata[VBR_HEADER_MIN_SIZE], vbr_header_size - VBR_HEADER_MIN_SIZE); |
| vbrheaderdata[vbr_header_size] = '\0'; |
| |
| // get frames, bytes, toc and scale |
| |
| if (vbr_flags & FRAMES_FLAG) |
| { |
| vbr_frames = ExtractI4(pvbrdata); |
| pvbrdata +=4; |
| } |
| |
| if (vbr_flags & BYTES_FLAG) |
| { |
| vbr_filesize = ExtractI4(pvbrdata); |
| pvbrdata +=4; |
| } |
| |
| if (vbr_flags & TOC_FLAG) |
| { |
| // seek offsets |
| // we are not using |
| // for(i=0;i<100;i++) seek_offsets[i] = pvbrdata[i]; |
| |
| pvbrdata +=100; |
| } |
| |
| if (vbr_flags & SCALE_FLAG) |
| { |
| vbr_scale = ExtractI4(pvbrdata); |
| pvbrdata +=4; |
| } |
| |
| if (vbr_frames > 0) |
| { |
| _mp3_header_output->vbr_bitrate = (((vbr_filesize!=0) ? vbr_filesize : mp3size) / vbr_frames) * _mp3_header_output->frequency / 144; |
| _mp3_header_output->vbr_bitrate -= _mp3_header_output->vbr_bitrate%1000; // round the bitrate: |
| } |
| } |
| } |
| } |
| |
| if (_mp3_header_output->framesize > 0 && mp3size >= _mp3_header_output->framesize) // this means bitrate is not none too |
| { |
| if (vbr_frames == 0) |
| _mp3_header_output->frames = fto_nearest_i((float)mp3size / _mp3_header_output->framesize); |
| else |
| _mp3_header_output->frames = vbr_frames; |
| |
| // bitrate becomes byterate (per second) if divided by 8 |
| if (_mp3_header_output->vbr_bitrate == 0) |
| _mp3_header_output->time = fto_nearest_i( (float)mp3size / (_mp3_header_output->bitrate / 8) ); |
| else |
| _mp3_header_output->time = fto_nearest_i( (float)mp3size / (_mp3_header_output->vbr_bitrate / 8) ); |
| } |
| else |
| { |
| _mp3_header_output->frames = 0; |
| _mp3_header_output->time = 0; |
| } |
| //if we got to here it's okay |
| return true; |
| } |
| |
| |