/* * mp3plot - Bitrate analysis tool * * Copyright (C) 2007 Toni Corvera * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // $Id: mp3_frame.cc 726 2007-05-20 14:27:08Z $ #include "mp3_frame.h" #include #include #include // ntohl, manual page (3) using std::ifstream; using std::string; using std::ios; using std::cout; namespace net_outlyer { namespace mp3 { // -- MP3 Frame -- const uint16_t mp3frame::FREE_FORM_BITRATE = 0; void mp3frame::read(ifstream & in) throw (e_error) { uint8_t sync_header[4]; in.read(CHARR(sync_header), 4); if (!in.good()) { throw e_read_failure(); } if (sync_header[0] != 0xff || (sync_header[1]&0xf0)!=0xf0) { throw e_read_failure("Invalid frame start.", EX_DATAERR); } assert( in.tellg() >= 4 ); start = -4; start += in.tellg(); // compiler complains if done in one operation // MPEG Version { // 2nd byte: xxxY Yxxx uint8_t version_field = (sync_header[1] & 0x18) >> 3; mpeg_version = static_cast(version_field); // 2nd byte: xxxx xYYx uint8_t layer_field = (sync_header[1] & 0x06) >> 1; mpeg_layer = static_cast(layer_field); // 2nd byte: xxxx xxxY crc = static_cast(sync_header[1] & 0x01); // 3rd byte: YYYY xxxx bitrate = get_bitrate( (sync_header[2] & 0xf0) >> 4 ); // 3rd byte: xxxx YYxx uint8_t freq_field = (sync_header[2] & 0x0c); switch (freq_field) { // FIXME: The table is different for MPEG-2 case 0: freq=44100; break; case 1: freq=48000; break; case 2: freq=32000; break; case 3: freq=0; break; // reserved default: // It CAN happen even for MPEG-1 (??) freq=-1; ; } // 3rd byte: xxxx xxYx has_padding = 0 != (sync_header[2] & 0x02); // 3rd byte: xxxx xxxY // Private bit, can be safely discarded // 4th byte: YYxx xxxx channel_mode = static_cast( (sync_header[3] & 0xc0)>>6 ); // TODO: parse the other fields } } void mp3frame::skip_data(ifstream & in) throw (e_read_failure) { uint32_t framelen = int(144 * (bitrate * 1000) / freq ) -4; if (has_padding) ++framelen; in.seekg(framelen, ios::cur); } void mp3frame_first::read(ifstream & in) throw (e_error) { mp3frame::read(in); using std::cerr; // POST ]-> At end of frame header (4th byte, [3]) check_vbr(in); if (!info.is_vbr) { cerr << "This appears to be a CBR file!\n" "\tIf the plot shows more than one bitrate either the file or\n" "\tthis program will be wrong." << std::endl; } // POST ]-> At 41th frame byte ([40]) || !is_vbr { // TODO: Is this right? in.seekg(3, std::ios_base::cur); in.read(CHARR(&flags), 1); } info.has_filesize = (flags & FL_FILESIZE); info.has_numframes = (flags & FL_NUMFRAMES); info.has_toc = (flags & FL_TOC); info.has_vbr_scale = (flags & FL_VBR_SCALE); // POST ]-> At 45th frame byte ([44]) { if (info.has_numframes) { in.read(CHARR(&info.num_frames), 4); // XXX: is this ok in all arches? info.num_frames = ntohl(info.num_frames); } else { in.seekg(4, ios::cur); } if (info.has_filesize) { in.read(CHARR(&info.file_size), 4); info.file_size = ntohl(info.file_size); } else { in.seekg(4, ios::cur); } // FIXME: Do something with the TOC ? in.seekg(99, ios::cur); // FIXME: Do something with VBR Scale ? in.seekg(4, ios::cur); } } void mp3frame_first::check_vbr(ifstream & in) throw (e_read_failure) { // PRE ]-> At 4th byte [3] const int BLOCK=36; uint8_t buffer[BLOCK]; in.read(CHARR(buffer), BLOCK); int offset=-1; while (!info.is_vbr && ++offset < (BLOCK-3)) { uint8_t * ptr = & (buffer[offset]); if (0 == memcmp(ptr, "Xing", 4)) { info.is_vbr = true; xing_offset = offset + 4; // We started after the first four bytes } } // FIXME: do validity check of offset // // // 36 "Xing" for MPEG1 and CHANNEL != mono (most used) // 21 "Xing" for MPEG1 and CHANNEL == mono // 21 "Xing" for MPEG2 and CHANNEL != mono // 13 "Xing" for MPEG2 and CHANNEL == mono if (!info.is_vbr) { // Return to the CBR frame start in.seekg( -40, std::ios_base::cur ); } } void mp3frame::dump() const { string ver, layer, chanmode; switch (mpeg_version) { case MPEG1: ver="MPEG v1"; break; case MPEG2: ver="MPEG v2"; break; case MPEG2_5: ver="MPEG v2.5"; break; default: ver="undefined"; } switch (mpeg_layer) { case LAYER_III: layer="Layer III"; break; case LAYER_II: layer="Layer II"; break; case LAYER_I: layer="Layer I"; break; default: layer="undefined"; } switch (channel_mode) { case STEREO: chanmode="Stereo"; break; case JOINT_STEREO: chanmode="Joint Stereo"; break; case DUAL_MONO: chanmode="Dual Mono"; break; case MONO: chanmode="Mono"; break; default: chanmode="undefined"; break; } cout << "Frame #" << frame_idx << "\n"; cout << "MPEG Version: " << ver << "\n"; cout << "MPEG Layer: " << layer << "\n"; cout << "Has CRC: " << (crc?"Yes":"No") << "\n"; // TODO: Do something on VBR files, this is always 128 and has no meaning AFAIK cout << "Nominal bitrate: " << bitrate << " kbps\n"; cout << "Sampling rate: " << freq << " Hz\n"; cout << "Channel mode: " << chanmode << "\n"; } /* * high_half_byte is the upper half of the byte shifted to * be the lower half */ uint16_t mp3frame::get_bitrate(uint8_t high_half_byte) { //uint16_t b; assert( (high_half_byte & 0xf0) == 0 ); return BITRATE_TABLE[high_half_byte]; } } // namespace mp3 } // namespace net_outlyer /* vim:set ts=4 et ai: */