You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
k3b/plugins/decoder/mp3/k3bmad.cpp

360 lines
8.2 KiB

/*
*
* $Id: k3bmad.cpp 619556 2007-01-03 17:38:12Z trueg $
* Copyright (C) 2004 Sebastian Trueg <trueg@k3b.org>
*
* This file is part of the K3b project.
* Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
*
* 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;
}