/* * * $Id: k3bmad.cpp 619556 2007-01-03 17:38:12Z trueg $ * Copyright (C) 2004 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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. * See the file "COPYING" for the exact licensing terms. */ #include "k3bmad.h" #include #include static const int INPUT_BUFFER_SIZE = 5*8192; K3bMad::K3bMad() : m_madStructuresInitialized(false), m_bInputError(false) { madStream = new mad_stream; madFrame = new mad_frame; madSynth = new mad_synth; madTimer = new mad_timer_t; // // we allocate additional MAD_BUFFER_GUARD bytes to always be able to append the // zero bytes needed for decoding the last frame. // m_inputBuffer = new unsigned char[INPUT_BUFFER_SIZE+MAD_BUFFER_GUARD]; } K3bMad::~K3bMad() { cleanup(); delete madStream; delete madFrame; delete madSynth; delete madTimer; delete [] m_inputBuffer; } bool K3bMad::open( const QString& filename ) { cleanup(); m_bInputError = false; m_channels = m_sampleRate = 0; m_inputFile.setName( filename ); if( !m_inputFile.open( IO_ReadOnly ) ) { kdError() << "(K3bMad) could not open file " << m_inputFile.name() << endl; return false; } initMad(); memset( m_inputBuffer, 0, INPUT_BUFFER_SIZE+MAD_BUFFER_GUARD ); return true; } bool K3bMad::inputError() const { return m_bInputError; } bool K3bMad::fillStreamBuffer() { /* The input bucket must be filled if it becomes empty or if * it's the first execution of the loop. */ if( madStream->buffer == 0 || madStream->error == MAD_ERROR_BUFLEN ) { if( eof() ) return false; long readSize, remaining; unsigned char* readStart; if( madStream->next_frame != 0 ) { remaining = madStream->bufend - madStream->next_frame; memmove( m_inputBuffer, madStream->next_frame, remaining ); readStart = m_inputBuffer + remaining; readSize = INPUT_BUFFER_SIZE - remaining; } else { readSize = INPUT_BUFFER_SIZE; readStart = m_inputBuffer; remaining = 0; } // Fill-in the buffer. Q_LONG result = m_inputFile.readBlock( (char*)readStart, readSize ); if( result < 0 ) { kdDebug() << "(K3bMad) read error on bitstream)" << endl; m_bInputError = true; return false; } else if( result == 0 ) { kdDebug() << "(K3bMad) end of input stream" << endl; return false; } else { readStart += result; if( eof() ) { kdDebug() << "(K3bMad::fillStreamBuffer) MAD_BUFFER_GUARD" << endl; memset( readStart, 0, MAD_BUFFER_GUARD ); result += MAD_BUFFER_GUARD; } // Pipe the new buffer content to libmad's stream decoder facility. mad_stream_buffer( madStream, m_inputBuffer, result + remaining ); madStream->error = MAD_ERROR_NONE; } } return true; } bool K3bMad::skipTag() { // skip the tag at the beginning of the file m_inputFile.at( 0 ); // // now check if the file starts with an id3 tag and skip it if so // char buf[4096]; int bufLen = 4096; if( m_inputFile.readBlock( buf, bufLen ) < bufLen ) { kdDebug() << "(K3bMad) unable to read " << bufLen << " bytes from " << m_inputFile.name() << endl; return false; } if( ( buf[0] == 'I' && buf[1] == 'D' && buf[2] == '3' ) && ( (unsigned short)buf[3] < 0xff && (unsigned short)buf[4] < 0xff ) ) { // do we have a footer? bool footer = (buf[5] & 0x10); // the size is saved as a synched int meaning bit 7 is always cleared to 0 unsigned int size = ( (buf[6] & 0x7f) << 21 ) | ( (buf[7] & 0x7f) << 14 ) | ( (buf[8] & 0x7f) << 7) | (buf[9] & 0x7f); unsigned int offset = size + 10; if( footer ) offset += 10; kdDebug() << "(K3bMad) skipping past ID3 tag to " << offset << endl; // skip the id3 tag if( !m_inputFile.at(offset) ) { kdDebug() << "(K3bMad) " << m_inputFile.name() << ": couldn't seek to " << offset << endl; return false; } } else { // reset file return m_inputFile.at( 0 ); } return true; } bool K3bMad::seekFirstHeader() { // // A lot of mp3 files start with a lot of junk which confuses mad. // We "allow" an mp3 file to start with at most 1 KB of junk. This is just // some random value since we do not want to search the hole file. That would // take way to long for non-mp3 files. // bool headerFound = findNextHeader(); QIODevice::Offset inputPos = streamPos(); while( !headerFound && !m_inputFile.atEnd() && streamPos() <= inputPos+1024 ) { headerFound = findNextHeader(); } // seek back to the begin of the frame if( headerFound ) { int streamSize = madStream->bufend - madStream->buffer; int bytesToFrame = madStream->this_frame - madStream->buffer; m_inputFile.at( m_inputFile.at() - streamSize + bytesToFrame ); kdDebug() << "(K3bMad) found first header at " << m_inputFile.at() << endl; } // reset the stream to make sure mad really starts decoding at out seek position mad_stream_finish( madStream ); mad_stream_init( madStream ); return headerFound; } bool K3bMad::eof() const { return m_inputFile.atEnd(); } QIODevice::Offset K3bMad::inputPos() const { return m_inputFile.at(); } QIODevice::Offset K3bMad::streamPos() const { return inputPos() - (madStream->bufend - madStream->this_frame + 1); } bool K3bMad::inputSeek( QIODevice::Offset pos ) { return m_inputFile.at( pos ); } void K3bMad::initMad() { if( !m_madStructuresInitialized ) { mad_stream_init( madStream ); mad_timer_reset( madTimer ); mad_frame_init( madFrame ); mad_synth_init( madSynth ); m_madStructuresInitialized = true; } } void K3bMad::cleanup() { if( m_inputFile.isOpen() ) { kdDebug() << "(K3bMad) cleanup at offset: " << "Input file at: " << m_inputFile.at() << " " << "Input file size: " << m_inputFile.size() << " " << "stream pos: " << ( m_inputFile.at() - (madStream->bufend - madStream->this_frame + 1) ) << endl; m_inputFile.close(); } if( m_madStructuresInitialized ) { mad_frame_finish( madFrame ); mad_synth_finish( madSynth ); mad_stream_finish( madStream ); } m_madStructuresInitialized = false; } // // LOSTSYNC could happen when mad encounters the id3 tag... // bool K3bMad::findNextHeader() { if( !fillStreamBuffer() ) return false; // // MAD_RECOVERABLE == true: frame was read, decoding failed (about to skip frame) // MAD_RECOVERABLE == false: frame was not read, need data // if( mad_header_decode( &madFrame->header, madStream ) < 0 ) { if( MAD_RECOVERABLE( madStream->error ) || madStream->error == MAD_ERROR_BUFLEN ) { return findNextHeader(); } else kdDebug() << "(K3bMad::findNextHeader) error: " << mad_stream_errorstr( madStream ) << endl; // FIXME probably we should not do this here since we don't do it // in the frame decoding // if( !checkFrameHeader( &madFrame->header ) ) // return findNextHeader(); return false; } if( !m_channels ) { m_channels = MAD_NCHANNELS(&madFrame->header); m_sampleRate = madFrame->header.samplerate; } mad_timer_add( madTimer, madFrame->header.duration ); return true; } bool K3bMad::decodeNextFrame() { if( !fillStreamBuffer() ) return false; if( mad_frame_decode( madFrame, madStream ) < 0 ) { if( MAD_RECOVERABLE( madStream->error ) || madStream->error == MAD_ERROR_BUFLEN ) { return decodeNextFrame(); } return false; } if( !m_channels ) { m_channels = MAD_NCHANNELS(&madFrame->header); m_sampleRate = madFrame->header.samplerate; } mad_timer_add( madTimer, madFrame->header.duration ); return true; } // // This is from the arts mad decoder // bool K3bMad::checkFrameHeader( mad_header* header ) const { int frameSize = MAD_NSBSAMPLES( header ) * 32; if( frameSize <= 0 ) return false; if( m_channels && m_channels != MAD_NCHANNELS(header) ) return false; return true; }