diff options
Diffstat (limited to 'plugins/decoder/mp3')
-rw-r--r-- | plugins/decoder/mp3/Makefile.am | 13 | ||||
-rw-r--r-- | plugins/decoder/mp3/configure.in.bot | 11 | ||||
-rw-r--r-- | plugins/decoder/mp3/configure.in.in | 24 | ||||
-rw-r--r-- | plugins/decoder/mp3/k3bmad.cpp | 359 | ||||
-rw-r--r-- | plugins/decoder/mp3/k3bmad.h | 92 | ||||
-rw-r--r-- | plugins/decoder/mp3/k3bmaddecoder.cpp | 542 | ||||
-rw-r--r-- | plugins/decoder/mp3/k3bmaddecoder.h | 79 | ||||
-rw-r--r-- | plugins/decoder/mp3/k3bmaddecoder.plugin | 9 |
8 files changed, 1129 insertions, 0 deletions
diff --git a/plugins/decoder/mp3/Makefile.am b/plugins/decoder/mp3/Makefile.am new file mode 100644 index 0000000..7f74d21 --- /dev/null +++ b/plugins/decoder/mp3/Makefile.am @@ -0,0 +1,13 @@ +AM_CPPFLAGS = -I$(srcdir)/../../../libk3b/core -I$(srcdir)/../../../libk3b/plugin -I$(srcdir)/../../../libk3bdevice $(taglib_includes) $(all_includes) + +kde_module_LTLIBRARIES = libk3bmaddecoder.la + +libk3bmaddecoder_la_SOURCES = k3bmad.cpp k3bmaddecoder.cpp + +libk3bmaddecoder_la_LIBADD = $(LIB_KDECORE) $(MAD_LIB) $(taglib_libs) ../../../libk3b/libk3b.la +libk3bmaddecoder_la_LDFLAGS = -avoid-version -module -no-undefined $(all_libraries) + +pluginsdir = $(kde_datadir)/k3b/plugins +plugins_DATA = k3bmaddecoder.plugin + +METASOURCES = AUTO diff --git a/plugins/decoder/mp3/configure.in.bot b/plugins/decoder/mp3/configure.in.bot new file mode 100644 index 0000000..0ee4872 --- /dev/null +++ b/plugins/decoder/mp3/configure.in.bot @@ -0,0 +1,11 @@ +echo "" + +if test -n "$MAD_LIB"; then + echo "K3b - Mp3 decoding support (libmad): yes" +else + echo "K3b - Mp3 decoding support (libmad): no" +if test "$ac_cv_use_libmad" = "yes"; then + echo "K3b - You are missing the libmad headers and libraries." + echo "K3b - The Mp3 decoding plugin won't be compiled." +fi +fi diff --git a/plugins/decoder/mp3/configure.in.in b/plugins/decoder/mp3/configure.in.in new file mode 100644 index 0000000..fb92936 --- /dev/null +++ b/plugins/decoder/mp3/configure.in.in @@ -0,0 +1,24 @@ +dnl === libmad MPEG decoder check - begin === +AC_ARG_WITH( + libmad, + AS_HELP_STRING([--without-libmad], [build K3b without libmad support (default=no)]), + [ac_cv_use_libmad=$withval], + [ac_cv_use_libmad=yes] +) + +if test "$ac_cv_use_libmad" = "yes"; then + MAD_LIB="" + KDE_CHECK_HEADER(mad.h, [ + AC_CHECK_LIB(mad, mad_synth_frame, [ + MAD_LIB="-lmad" + AC_DEFINE(HAVE_LIBMAD,1,[defined if you have libmad headers and libraries])], + [], + $all_libraries + ) + ]) + AC_SUBST(MAD_LIB) + +fi + +AM_CONDITIONAL(include_MP3, [test -n "$MAD_LIB"]) +dnl === libmad MPeg decoder check - end === 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; +} + + diff --git a/plugins/decoder/mp3/k3bmad.h b/plugins/decoder/mp3/k3bmad.h new file mode 100644 index 0000000..a4d9aae --- /dev/null +++ b/plugins/decoder/mp3/k3bmad.h @@ -0,0 +1,92 @@ +/* + * + * $Id: k3bmad.h 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. + */ + +#ifndef _K3B_MAD_H_ +#define _K3B_MAD_H_ + +extern "C" { +#include <mad.h> +} + +#include <qfile.h> + + +class K3bMad +{ +public: + K3bMad(); + ~K3bMad(); + + bool open( const QString& filename ); + + /** + * @return true if the mad stream contains data + * false if there is no data left or an error occurred. + * In the latter case inputError() returns true. + */ + bool fillStreamBuffer(); + + /** + * Skip id3 tags. + * + * This will reset the input file. + */ + bool skipTag(); + + /** + * Find first frame and seek to the beginning of that frame. + * This is used to skip the junk that many mp3 files start with. + */ + bool seekFirstHeader(); + + bool eof() const; + bool inputError() const; + + /** + * Current position in theinput file. This does NOT + * care about the status of the mad stream. Use streamPos() + * in that case. + */ + QIODevice::Offset inputPos() const; + + /** + * Current absolut position of the decoder stream. + */ + QIODevice::Offset streamPos() const; + bool inputSeek( QIODevice::Offset pos ); + + void initMad(); + void cleanup(); + + bool decodeNextFrame(); + bool findNextHeader(); + bool checkFrameHeader( mad_header* header ) const; + + mad_stream* madStream; + mad_frame* madFrame; + mad_synth* madSynth; + mad_timer_t* madTimer; + +private: + QFile m_inputFile; + bool m_madStructuresInitialized; + unsigned char* m_inputBuffer; + bool m_bInputError; + + int m_channels; + int m_sampleRate; +}; + +#endif diff --git a/plugins/decoder/mp3/k3bmaddecoder.cpp b/plugins/decoder/mp3/k3bmaddecoder.cpp new file mode 100644 index 0000000..e3aef56 --- /dev/null +++ b/plugins/decoder/mp3/k3bmaddecoder.cpp @@ -0,0 +1,542 @@ +/* + * + * $Id$ + * Copyright (C) 2003 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. + */ + + +// +// Some notes on mp3: +// A mp3 Frame is always samples/samplerate seconds in length +// +// +// +// What we need are raw 16 bit stereo samples at 44100 Hz which results in 588 samples +// per block (2352 bytes: 32*588 bit). 1 second are 75 blocks. +// + +#include <config.h> + +#include "k3bmaddecoder.h" +#include "k3bmad.h" + +#include <k3bpluginfactory.h> + +#include <kurl.h> +#include <kdebug.h> +#include <klocale.h> + +#include <qstring.h> +#include <qfile.h> +#include <qvaluevector.h> + +#include <stdlib.h> +#include <cmath> +#include <cstdlib> + +#include <config.h> + +#ifdef HAVE_TAGLIB +#include <taglib/tag.h> +#include <taglib/mpegfile.h> +#endif + + +K_EXPORT_COMPONENT_FACTORY( libk3bmaddecoder, K3bPluginFactory<K3bMadDecoderFactory>( "k3bmaddecoder" ) ) + + +int K3bMadDecoder::MaxAllowedRecoverableErrors = 10; + + + +class K3bMadDecoder::MadDecoderPrivate +{ +public: + MadDecoderPrivate() + : outputBuffer(0), + outputPointer(0), + outputBufferEnd(0) { + mad_header_init( &firstHeader ); + } + + K3bMad* handle; + + QValueVector<unsigned long long> seekPositions; + + bool bOutputFinished; + + char* outputBuffer; + char* outputPointer; + char* outputBufferEnd; + + // the first frame header for technical info + mad_header firstHeader; + bool vbr; +}; + + + + +K3bMadDecoder::K3bMadDecoder( QObject* parent, const char* name ) + : K3bAudioDecoder( parent, name ) +{ + d = new MadDecoderPrivate(); + d->handle = new K3bMad(); +} + + +K3bMadDecoder::~K3bMadDecoder() +{ + cleanup(); + delete d->handle; + delete d; +} + + +QString K3bMadDecoder::metaInfo( MetaDataField f ) +{ +#ifdef HAVE_TAGLIB + TagLib::MPEG::File file( QFile::encodeName( filename() ).data() ); + + if ( file.tag() ) { + switch( f ) { + case META_TITLE: + return TStringToQString( file.tag()->title() ); + case META_ARTIST: + return TStringToQString( file.tag()->artist() ); + case META_COMMENT: + return TStringToQString( file.tag()->comment() ); + default: + return QString::null; + } + } + else { + return QString::null; + } + +#else + return K3bAudioDecoder::metaInfo( f ); +#endif +} + + +bool K3bMadDecoder::analyseFileInternal( K3b::Msf& frames, int& samplerate, int& ch ) +{ + initDecoderInternal(); + frames = countFrames(); + if( frames > 0 ) { + // we convert mono to stereo all by ourselves. :) + ch = 2; + samplerate = d->firstHeader.samplerate; + return true; + } + else + return false; +} + + +bool K3bMadDecoder::initDecoderInternal() +{ + cleanup(); + + d->bOutputFinished = false; + + if( !d->handle->open( filename() ) ) + return false; + + if( !d->handle->skipTag() ) + return false; + + if( !d->handle->seekFirstHeader() ) + return false; + + return true; +} + + +unsigned long K3bMadDecoder::countFrames() +{ + kdDebug() << "(K3bMadDecoder::countFrames)" << endl; + + unsigned long frames = 0; + bool error = false; + d->vbr = false; + bool bFirstHeaderSaved = false; + + d->seekPositions.clear(); + + while( !error && d->handle->findNextHeader() ) { + + if( !bFirstHeaderSaved ) { + bFirstHeaderSaved = true; + d->firstHeader = d->handle->madFrame->header; + } + else if( d->handle->madFrame->header.bitrate != d->firstHeader.bitrate ) + d->vbr = true; + + // + // position in stream: postion in file minus the not yet used buffer + // + unsigned long long seekPos = d->handle->inputPos() - + (d->handle->madStream->bufend - d->handle->madStream->this_frame + 1); + + // save the number of bytes to be read to decode i-1 frames at position i + // in other words: when seeking to seekPos the next decoded frame will be i + d->seekPositions.append( seekPos ); + } + + if( !d->handle->inputError() && !error ) { + // we need the length of the track to be multiple of frames (1/75 second) + float seconds = (float)d->handle->madTimer->seconds + + (float)d->handle->madTimer->fraction/(float)MAD_TIMER_RESOLUTION; + frames = (unsigned long)ceil(seconds * 75.0); + kdDebug() << "(K3bMadDecoder) length of track " << seconds << endl; + } + + cleanup(); + + kdDebug() << "(K3bMadDecoder::countFrames) end" << endl; + + return frames; +} + + +int K3bMadDecoder::decodeInternal( char* _data, int maxLen ) +{ + d->outputBuffer = _data; + d->outputBufferEnd = d->outputBuffer + maxLen; + d->outputPointer = d->outputBuffer; + + bool bOutputBufferFull = false; + + while( !bOutputBufferFull && d->handle->fillStreamBuffer() ) { + + // a mad_synth contains of the data of one mad_frame + // one mad_frame represents a mp3-frame which is always 1152 samples + // for us that means we need 4*1152 bytes of output buffer for every frame + // since one sample has 16 bit + if( d->outputBufferEnd - d->outputPointer < 4*1152 ) { + bOutputBufferFull = true; + } + else if( d->handle->decodeNextFrame() ) { + // + // Once decoded the frame is synthesized to PCM samples. No errors + // are reported by mad_synth_frame(); + // + mad_synth_frame( d->handle->madSynth, d->handle->madFrame ); + + // this fills the output buffer + if( !createPcmSamples( d->handle->madSynth ) ) { + return -1; + } + } + else if( d->handle->inputError() ) { + return -1; + } + } + + // flush the output buffer + size_t buffersize = d->outputPointer - d->outputBuffer; + + return buffersize; +} + + +unsigned short K3bMadDecoder::linearRound( mad_fixed_t fixed ) +{ + // round + fixed += (1L << ( MAD_F_FRACBITS - 16 )); + + // clip + if( fixed >= MAD_F_ONE - 1 ) + fixed = MAD_F_ONE - 1; + else if( fixed < -MAD_F_ONE ) + fixed = -MAD_F_ONE; + + // quatisize + return fixed >> (MAD_F_FRACBITS + 1 - 16 ); +} + + +bool K3bMadDecoder::createPcmSamples( mad_synth* synth ) +{ + unsigned short nsamples = synth->pcm.length; + + // this should not happen since we only decode if the + // output buffer has enough free space + if( d->outputBufferEnd - d->outputPointer < nsamples*4 ) { + kdDebug() << "(K3bMadDecoder) buffer overflow!" << endl; + return false; + } + + // now create the output + for( int i = 0; i < nsamples; i++ ) { + + /* Left channel */ + unsigned short sample = linearRound( synth->pcm.samples[0][i] ); + *(d->outputPointer++) = (sample >> 8) & 0xff; + *(d->outputPointer++) = sample & 0xff; + + /* Right channel. If the decoded stream is monophonic then + * the right output channel is the same as the left one. + */ + if( synth->pcm.channels == 2 ) + sample = linearRound( synth->pcm.samples[1][i] ); + + *(d->outputPointer++) = (sample >> 8) & 0xff; + *(d->outputPointer++) = sample & 0xff; + } // pcm conversion + + return true; +} + + +void K3bMadDecoder::cleanup() +{ + d->handle->cleanup(); +} + + +bool K3bMadDecoder::seekInternal( const K3b::Msf& pos ) +{ + // + // we need to reset the complete mad stuff + // + if( !initDecoderInternal() ) + return false; + + // + // search a position + // This is all hacking, I don't really know what I am doing here... ;) + // + double mp3FrameSecs = static_cast<double>(d->firstHeader.duration.seconds) + + static_cast<double>(d->firstHeader.duration.fraction) / static_cast<double>(MAD_TIMER_RESOLUTION); + + double posSecs = static_cast<double>(pos.totalFrames()) / 75.0; + + // seekPosition to seek after frame i + unsigned int frame = static_cast<unsigned int>( posSecs / mp3FrameSecs ); + + // Rob said: 29 frames is the theoretically max frame reservoir limit (whatever that means...) + // it seems that mad needs at most 29 frames to get ready + unsigned int frameReservoirProtect = ( frame > 29 ? 29 : frame ); + + frame -= frameReservoirProtect; + + // seek in the input file behind the already decoded data + d->handle->inputSeek( d->seekPositions[frame] ); + + kdDebug() << "(K3bMadDecoder) Seeking to frame " << frame << " with " + << frameReservoirProtect << " reservoir frames." << endl; + + // decode some frames ignoring MAD_ERROR_BADDATAPTR errors + unsigned int i = 1; + while( i <= frameReservoirProtect ) { + d->handle->fillStreamBuffer(); + if( mad_frame_decode( d->handle->madFrame, d->handle->madStream ) ) { + if( MAD_RECOVERABLE( d->handle->madStream->error ) ) { + if( d->handle->madStream->error == MAD_ERROR_BUFLEN ) + continue; + else if( d->handle->madStream->error != MAD_ERROR_BADDATAPTR ) { + kdDebug() << "(K3bMadDecoder) Seeking: recoverable mad error (" + << mad_stream_errorstr(d->handle->madStream) << ")" << endl; + continue; + } + else { + kdDebug() << "(K3bMadDecoder) Seeking: ignoring (" + << mad_stream_errorstr(d->handle->madStream) << ")" << endl; + } + } + else + return false; + } + + if( i == frameReservoirProtect ) // synth only the last frame (Rob said so ;) + mad_synth_frame( d->handle->madSynth, d->handle->madFrame ); + + ++i; + } + + return true; +} + + +QString K3bMadDecoder::fileType() const +{ + switch( d->firstHeader.layer ) { + case MAD_LAYER_I: + return "MPEG1 Layer I"; + case MAD_LAYER_II: + return "MPEG1 Layer II"; + case MAD_LAYER_III: + return "MPEG1 Layer III"; + default: + return "Mp3"; + } +} + +QStringList K3bMadDecoder::supportedTechnicalInfos() const +{ + return QStringList::split( ";", + i18n("Channels") + ";" + + i18n("Sampling Rate") + ";" + + i18n("Bitrate") + ";" + + i18n("Layer") + ";" + + i18n("Emphasis") + ";" + + i18n("Copyright") + ";" + + i18n("Original") + ";" + + i18n("CRC") ); +} + + +QString K3bMadDecoder::technicalInfo( const QString& name ) const +{ + if( name == i18n("Channels") ) { + switch( d->firstHeader.mode ) { + case MAD_MODE_SINGLE_CHANNEL: + return i18n("Mono"); + case MAD_MODE_DUAL_CHANNEL: + return i18n("Dual"); + case MAD_MODE_JOINT_STEREO: + return i18n("Joint Stereo"); + case MAD_MODE_STEREO: + return i18n("Stereo"); + default: + return "?"; + } + } + else if( name == i18n("Sampling Rate") ) + return i18n("%1 Hz").arg(d->firstHeader.samplerate); + else if( name == i18n("Bitrate") ) { + if( d->vbr ) + return i18n("VBR"); + else + return i18n("%1 bps").arg(d->firstHeader.bitrate); + } + else if( name == i18n("Layer") ){ + switch( d->firstHeader.layer ) { + case MAD_LAYER_I: + return "I"; + case MAD_LAYER_II: + return "II"; + case MAD_LAYER_III: + return "III"; + default: + return "?"; + } + } + else if( name == i18n("Emphasis") ) { + switch( d->firstHeader.emphasis ) { + case MAD_EMPHASIS_NONE: + return i18n("None"); + case MAD_EMPHASIS_50_15_US: + return i18n("50/15 ms"); + case MAD_EMPHASIS_CCITT_J_17: + return i18n("CCITT J.17"); + default: + return i18n("Unknown"); + } + } + else if( name == i18n("Copyright") ) + return ( d->firstHeader.flags & MAD_FLAG_COPYRIGHT ? i18n("Yes") : i18n("No") ); + else if( name == i18n("Original") ) + return ( d->firstHeader.flags & MAD_FLAG_ORIGINAL ? i18n("Yes") : i18n("No") ); + else if( name == i18n("CRC") ) + return ( d->firstHeader.flags & MAD_FLAG_PROTECTION ? i18n("Yes") : i18n("No") ); + else + return QString::null; +} + + +K3bMadDecoderFactory::K3bMadDecoderFactory( QObject* parent, const char* name ) + : K3bAudioDecoderFactory( parent, name ) +{ +} + + +K3bMadDecoderFactory::~K3bMadDecoderFactory() +{ +} + + +K3bAudioDecoder* K3bMadDecoderFactory::createDecoder( QObject* parent, + const char* name ) const +{ + return new K3bMadDecoder( parent, name ); +} + + +bool K3bMadDecoderFactory::canDecode( const KURL& url ) +{ + // + // HACK: + // + // I am simply no good at this and this detection code is no good as well + // It always takes waves for mp3 files so we introduce this hack to + // filter out wave files. :( + // + QFile f( url.path() ); + if( !f.open( IO_ReadOnly ) ) + return false; + char buffer[12]; + if( f.readBlock( buffer, 12 ) != 12 ) + return false; + if( !qstrncmp( buffer, "RIFF", 4 ) && + !qstrncmp( buffer + 8, "WAVE", 4 ) ) + return false; + f.close(); + + + K3bMad handle; + if( !handle.open( url.path() ) ) + return false; + + handle.skipTag(); + if( !handle.seekFirstHeader() ) + return false; + + if( handle.findNextHeader() ) { + int c = MAD_NCHANNELS( &handle.madFrame->header ); + int layer = handle.madFrame->header.layer; + unsigned int s = handle.madFrame->header.samplerate; + + // + // find 4 more mp3 headers (random value since 2 was not enough) + // This way we get most of the mp3 files while sorting out + // for example wave files. + // + int cnt = 1; + while( handle.findNextHeader() ) { + // compare the found headers + if( MAD_NCHANNELS( &handle.madFrame->header ) == c && + handle.madFrame->header.layer == layer && + handle.madFrame->header.samplerate == s ) { + // only support layer III for now since otherwise some wave files + // are taken for layer I + if( ++cnt >= 5 ) { + kdDebug() << "(K3bMadDecoder) valid mpeg 1 layer " << layer + << " file with " << c << " channels and a samplerate of " + << s << endl; + return ( layer == MAD_LAYER_III ); + } + } + else + break; + } + } + + kdDebug() << "(K3bMadDecoder) unsupported format: " << url.path() << endl; + + return false; +} + +#include "k3bmaddecoder.moc" diff --git a/plugins/decoder/mp3/k3bmaddecoder.h b/plugins/decoder/mp3/k3bmaddecoder.h new file mode 100644 index 0000000..91e0f6c --- /dev/null +++ b/plugins/decoder/mp3/k3bmaddecoder.h @@ -0,0 +1,79 @@ +/* + * + * $Id$ + * Copyright (C) 2003 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. + */ + +#ifndef K3BMP3MODULE_H +#define K3BMP3MODULE_H + + +#include <k3baudiodecoder.h> + +extern "C" { +#include <mad.h> +} + + +class K3bMadDecoderFactory : public K3bAudioDecoderFactory +{ + Q_OBJECT + + public: + K3bMadDecoderFactory( QObject* parent = 0, const char* name = 0 ); + ~K3bMadDecoderFactory(); + + bool canDecode( const KURL& filename ); + + int pluginSystemVersion() const { return 3; } + + K3bAudioDecoder* createDecoder( QObject* parent = 0, + const char* name = 0 ) const; +}; + + +class K3bMadDecoder : public K3bAudioDecoder +{ + Q_OBJECT + + public: + K3bMadDecoder( QObject* parent = 0, const char* name = 0 ); + ~K3bMadDecoder(); + + QString metaInfo( MetaDataField ); + + void cleanup(); + + bool seekInternal( const K3b::Msf& ); + + QString fileType() const; + QStringList supportedTechnicalInfos() const; + QString technicalInfo( const QString& ) const; + + protected: + bool analyseFileInternal( K3b::Msf& frames, int& samplerate, int& ch ); + bool initDecoderInternal(); + + int decodeInternal( char* _data, int maxLen ); + + private: + unsigned long countFrames(); + inline unsigned short linearRound( mad_fixed_t fixed ); + bool createPcmSamples( mad_synth* ); + + static int MaxAllowedRecoverableErrors; + + class MadDecoderPrivate; + MadDecoderPrivate* d; +}; + +#endif diff --git a/plugins/decoder/mp3/k3bmaddecoder.plugin b/plugins/decoder/mp3/k3bmaddecoder.plugin new file mode 100644 index 0000000..69fbbb8 --- /dev/null +++ b/plugins/decoder/mp3/k3bmaddecoder.plugin @@ -0,0 +1,9 @@ +[K3b Plugin] +Lib=libk3bmaddecoder +Group=AudioDecoder +Name=K3b MAD Decoder +Author=Sebastian Trueg +Version=3.1 +Comment=Decoding module to decode MPEG 1 Layer III files +License=GPL |