/* aKode: MPEG-Decoder (MAD-decoder) Copyright (C) 2004 Allan Sandfeld Jensen 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., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akodelib.h" // uncomment to debug the plugin //#define MPEG_DEBUG #include #include #ifdef MPEG_DEBUG #include using std::cerr; #endif #include "file.h" #include "audioframe.h" #include "mpeg_decoder.h" namespace aKode { bool MPEGDecoderPlugin::canDecode(File* src) { char header[6]; unsigned char *buf = (unsigned char*)header; // C-stuff bool res = false; src->openRO(); if(src->read(header, 4)) { // skip id3v2 headers if (memcmp(header, "ID3", 3) == 0) { src->read(header, 6); int size = 0; if (buf[1] & 0x10) size += 10; size += buf[5]; size += buf[4] << 7; size += buf[3] << 14; size += buf[2] << 21; src->seek(10+size); src->read(header, 4); } if (buf[0] == 0xff && (buf[1] & 14)) // frame synchronizer if((buf[1] & 0x18) != 0x08) // support MPEG 1, 2 and 2½ if((buf[1] & 0x06) != 0x00) // Layer I, II and III res = true; } src->close(); return res; } extern "C" { MPEGDecoderPlugin mpeg_decoder; } struct xing_frame { // Xing header: bool has_toc; // valid table-of-contents unsigned int bytes; // nr-of-bytes (0 if unknown) unsigned int frames; // nr-of-frames (0 if unknown) unsigned char toc[100]; // time: x/100 -> place: y/256 (y = toc[x]) }; #define FILEBUFFER_SIZE 8192 struct MPEGDecoder::private_data { private_data() : position(0), length(0), bitrate(0), eof(false), error(false), initialized(false), id3v2size(0), xing_vbr(false), unknown_vbr(false) {}; mad_stream stream; mad_frame frame; mad_synth synth; File *src; AudioConfiguration config; long position, length; int bitrate, layer; bool eof, error, initialized; int id3v2size; // size of prepended ID3v2 tag bool xing_vbr; // Xing-compatible VBR bool unknown_vbr; // Oh, bugger... xing_frame xing; char filebuffer[FILEBUFFER_SIZE]; bool metaframe_filter(bool fast); }; MPEGDecoder::MPEGDecoder(File *src) { m_data = new private_data(); mad_stream_init(&m_data->stream); mad_frame_init(&m_data->frame); mad_synth_init(&m_data->synth); mad_stream_options(&m_data->stream, MAD_OPTION_IGNORECRC); m_data->eof = false; m_data->error = false; m_data->src = src; if (!m_data->src->openRO()) { m_data->error = true; #ifdef MPEG_DEBUG cerr << "Could not open file: " << m_data->src->filename << "\n"; #endif } m_data->src->fadvise(); m_data->length = m_data->src->length(); } MPEGDecoder::~MPEGDecoder() { mad_stream_finish(&m_data->stream); mad_frame_finish(&m_data->frame); mad_synth_finish(&m_data->synth); m_data->src->close(); delete m_data; } bool MPEGDecoder::skipID3v2() { m_data->src->seek(0); m_data->id3v2size = 0; char buf[16]; if (m_data->src->read(buf, 10)) { // skip id3v2 headers if (memcmp(buf, "ID3", 3) == 0) { #ifdef MPEG_DEBUG cerr << "ID3v2 tag found\n"; #endif int size = 0; if (buf[5] & 0x10) size += 10; size += buf[9]; size += buf[8] << 7; size += buf[7] << 14; size += buf[6] << 21; m_data->id3v2size = size; if (!m_data->src->seek(10+size)) { char space[256]; for(int i=0; isrc->read(space, (size-i) > 256 ? 256 : (size-i)); } } return true; } } if (!m_data->src->seek(0)) { mad_stream_buffer(&m_data->stream, (uint8_t*)buf, 10); } return false; } bool MPEGDecoder::moreData(bool flush) { mad_stream *stream = &m_data->stream; long rem = 0; if (stream->next_frame != 0 && !flush) { rem = stream->bufend - stream->next_frame; memmove(m_data->filebuffer, stream->next_frame, rem); } #ifdef MPEG_DEBUG // cerr << "remainder=" << rem << "\n"; #endif int res = m_data->src->read(m_data->filebuffer+rem, FILEBUFFER_SIZE-rem); mad_stream_buffer(stream, (uint8_t*)m_data->filebuffer, res+rem); // for some reason MAD can't to do this itself: if (stream->error == MAD_ERROR_LOSTSYNC || flush) stream->sync = 0; stream->error = MAD_ERROR_NONE; if (res == 0) { m_data->eof = true; return false; } else if (res < 0) { m_data->error = true; return false; } else return true; } // originaly from minimad.c template static inline int32_t scale(mad_fixed_t sample) { /* round */ sample += (1L << (MAD_F_FRACBITS - bits)); /* clip */ if (sample >= MAD_F_ONE) sample = MAD_F_ONE - 1; else if (sample < -MAD_F_ONE) sample = -MAD_F_ONE; /* quantize */ return sample >> (MAD_F_FRACBITS + 1 - bits); } static inline long postotime(long pos, int sample_rate) { if (sample_rate == 0) return 0; long sec = pos/sample_rate; long rem = pos%sample_rate; long ms = (rem*1000)/sample_rate; return sec*1000+ms; } // Xing-header documentation is from http://www.multiweb.cz/twoinches/MP3inside.htm // toc-format verified in LAME source-code static bool xing_decode(xing_frame *xing, const unsigned char * bytes) { mad_bitptr ptr; mad_bit_init(&ptr, bytes); if (mad_bit_read(&ptr, 32) == (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g')) { int flags = mad_bit_read(&ptr, 32); if (flags & 1) xing->frames = mad_bit_read(&ptr, 32); if (flags & 2) xing->bytes = mad_bit_read(&ptr, 32); if (flags & 4) { xing->has_toc = true; for (int i=0; i<100; i++) { xing->toc[i] = mad_bit_read(&ptr, 8); } } return true; } return false; } // Detect various metaframes bool MPEGDecoder::private_data::metaframe_filter(bool fast) { if (stream.anc_bitlen < 16) return false; const unsigned char* ptr; ptr = stream.anc_ptr.byte; test: if ((ptr[0] == 'X') && (ptr[1] == 'i') && (ptr[2] == 'n') && (ptr[3] =='g')) { #ifdef MPEG_DEBUG cerr << "Xing(VBR) header found\n"; #endif xing_vbr = true; xing_decode(&xing, ptr); return true; } /* if ((ptr[0] == 'L') || (ptr[1] == 'A') || (ptr[2] == 'M') || (ptr[3] =='E')) { #ifdef MPEG_DEBUG cerr << "LAME frame found\n"; #endif return true; }*/ if ((ptr[0] == 'I') && (ptr[1] == 'n') && (ptr[2] == 'f') && (ptr[3] =='o')) { #ifdef MPEG_DEBUG cerr << "Info(CBR) header found\n"; #endif return true; } if (!fast) { // We can't trust broken-encoders to _not_ fuck this up ptr = stream.this_frame; // skip frame header and side info ptr += 6; // Search for metaframe tags: for(int i=0; i<64 && ptr < stream.bufend; i++) { if (*ptr == 'X' || *ptr== 'I') { fast = true; goto test; } ptr++; } } return false; } bool MPEGDecoder::sync() { // only try 32 times, if no sync-frame is found the file is properbly not MPEG for(int i=0; i<32; i++) { int res = mad_stream_sync(&m_data->stream); if (res == 0 ) goto got_sync; if (!moreData(true)) break; } m_data->error=true; #ifdef MPEG_DEBUG cerr << "Synchronization failed, likely not a mpeg audio file.\n"; #endif return false; got_sync: return true; } bool MPEGDecoder::prepare() { skipID3v2(); // initialize stream if (m_data->stream.buffer == 0) moreData(); if (sync()) { m_data->initialized = true; return true; } else return false; } static void setChannelConfiguration(AudioConfiguration& config, mad_mode mode) { switch(mode) { case MAD_MODE_SINGLE_CHANNEL: config.channels=1; config.channel_config=MonoStereo; break; case MAD_MODE_DUAL_CHANNEL: config.channels=2; config.channel_config=MultiChannel; break; default: // Stereo and joint stereo config.channels=2; config.channel_config=MonoStereo; } config.surround_config = 0; } bool MPEGDecoder::readFrame(AudioFrame* frame) { if (m_data->error) return false; int res = 0; if (!m_data->initialized) { if (!prepare()) return false; int retries = 0; first_frame: if (retries >= 8) { // fatal error m_data->error = true; return false; } res = mad_frame_decode(&m_data->frame, &m_data->stream); if (res != 0) { if (m_data->stream.error == MAD_ERROR_BUFLEN) { retries++; if (!moreData(true)) return false; } else if (m_data->stream.error == MAD_ERROR_LOSTSYNC) {} else if (MAD_RECOVERABLE(m_data->stream.error)) { #ifdef MPEG_DEBUG cerr << "Invalid frame: " << mad_stream_errorstr(&m_data->stream) << "\n"; #endif m_data->metaframe_filter(true); } goto first_frame; } m_data->config.sample_rate = m_data->frame.header.samplerate; m_data->config.sample_width = 16; setChannelConfiguration(m_data->config, m_data->frame.header.mode); m_data->bitrate = m_data->frame.header.bitrate; m_data->layer = m_data->frame.header.layer; m_data->metaframe_filter(false); } else { if (m_data->stream.buffer == 0 || m_data->stream.error==MAD_ERROR_BUFLEN || m_data->stream.error==MAD_ERROR_LOSTSYNC) if (!moreData()) return false; int retries = 0; bool sync = true; retry: // give up after 16 new pages if (retries >= 16) { // fatal error m_data->error = true; return false; } res = mad_frame_decode(&m_data->frame, &m_data->stream); if (res != 0) { if (m_data->stream.error == MAD_ERROR_BUFLEN) { retries++; if (moreData(!sync)) return false; goto retry; } else if (m_data->stream.error == MAD_ERROR_LOSTSYNC) { #ifdef MPEG_DEBUG cerr << "No sync\n"; #endif sync = false; goto retry; } else if (MAD_RECOVERABLE(m_data->stream.error)) { // invalid frame #ifdef MPEG_DEBUG cerr << "Invalid frame: " << mad_stream_errorstr(&m_data->stream) << "\n"; #endif m_data->metaframe_filter(true); goto retry; } else { // Unrecoverable error m_data->error = true; return false; } } // fast check for metaframes if (m_data->metaframe_filter(false)) goto retry; } if (m_data->bitrate && !m_data->xing_vbr) if (m_data->bitrate != (int)m_data->frame.header.bitrate) m_data->unknown_vbr = true; mad_synth_frame(&m_data->synth, &m_data->frame); mad_pcm *pcm = &m_data->synth.pcm; // Has the format changed? (misread initial frame) m_data->config.sample_rate = pcm->samplerate; setChannelConfiguration(m_data->config, m_data->frame.header.mode); int channels = pcm->channels; long length = pcm->length; frame->reserveSpace(&m_data->config, length); // Scale into frame (could be reduced to copy if we used MADs internal format) int16_t** data = (int16_t**)frame->data; for(int j=0; j(pcm->samples[j][i]); m_data->position += length; frame->pos = postotime(m_data->position, m_data->config.sample_rate); return true; } static inline long time_madtoakode(mad_timer_t time) { return mad_timer_count(time, MAD_UNITS_MILLISECONDS); } static float mpeg_length(MPEGDecoder::private_data *m_data) { if (m_data->layer == 1) return (m_data->xing.frames*384.0)/m_data->config.sample_rate; else return (m_data->xing.frames*1152.0)/m_data->config.sample_rate; } long MPEGDecoder::length() { float len; if (!m_data->initialized) return -1; else { if (m_data->xing_vbr || m_data->unknown_vbr) { if (m_data->xing_vbr && m_data->xing.frames > 0) { len = mpeg_length(m_data)*1000.0; } else { // VBR shot in the dark (based on average bitrate) if (m_data->length > 0) { float bytepos = m_data->src->position()/(float)m_data->length; len = position()/bytepos; } else len = 0; } } else { if (m_data->length > 0) len = ((float)m_data->length*8000.0)/((float)m_data->bitrate); else len = 0; } } return (long)(len); } long MPEGDecoder::position() { if (!m_data->initialized) return -1; if (m_data->frame.header.samplerate > 0 && m_data->position >= 0) return postotime(m_data->position, m_data->config.sample_rate); else return -1; } bool MPEGDecoder::eof() { return m_data->eof; } bool MPEGDecoder::error() { return m_data->error; } bool MPEGDecoder::seekable() { return m_data->src->seekable(); } bool MPEGDecoder::seek(long pos) { if (!m_data->initialized) return false; bool res = false; // VBR with toc: if (m_data->xing_vbr && m_data->xing.has_toc && m_data->xing.frames > 0) { int procent = (int)((float)pos/(mpeg_length(m_data)*10)); float place = ((float)m_data->xing.toc[procent])/256.0; long bpos = (long)(place * m_data->length); #ifdef MPEG_DEBUG cerr << "TOC seeking " << procent << "/100 -> " << (long)(place*256.0) << "/256\n" ; #endif res = m_data->src->seek(bpos); if (res) m_data->position = (long)(((float)procent/100.0)*mpeg_length(m_data)*m_data->config.sample_rate); } else { // CBR seeking (and VBR without TOC fallback) #ifdef MPEG_DEBUG cerr << "CBR seeking\n"; #endif long bpos = (long)((((float)pos)*m_data->bitrate)/8000.0); bpos += m_data->id3v2size; res = m_data->src->seek(bpos); if (res) m_data->position = (long)(((float)pos*(float)m_data->config.sample_rate)/1000.0); } if (res) { moreData(true); sync(); } return res; } const AudioConfiguration* MPEGDecoder::audioConfiguration() { if (!m_data->initialized) return 0; return &m_data->config; } } // namespace