diff options
Diffstat (limited to 'plugins/decoder/mp3/k3bmad.cpp')
-rw-r--r-- | plugins/decoder/mp3/k3bmad.cpp | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/plugins/decoder/mp3/k3bmad.cpp b/plugins/decoder/mp3/k3bmad.cpp new file mode 100644 index 0000000..cb4cf6c --- /dev/null +++ b/plugins/decoder/mp3/k3bmad.cpp @@ -0,0 +1,359 @@ +/* + * + * $Id: k3bmad.cpp 619556 2007-01-03 17:38:12Z trueg $ + * Copyright (C) 2004 Sebastian Trueg <[email protected]> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <[email protected]> + * + * 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 <qfile.h> +#include <kdebug.h> + + +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; +} + + |