summaryrefslogtreecommitdiffstats
path: root/plugins/decoder/mp3
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/decoder/mp3')
-rw-r--r--plugins/decoder/mp3/Makefile.am13
-rw-r--r--plugins/decoder/mp3/configure.in.bot11
-rw-r--r--plugins/decoder/mp3/configure.in.in24
-rw-r--r--plugins/decoder/mp3/k3bmad.cpp359
-rw-r--r--plugins/decoder/mp3/k3bmad.h92
-rw-r--r--plugins/decoder/mp3/k3bmaddecoder.cpp542
-rw-r--r--plugins/decoder/mp3/k3bmaddecoder.h79
-rw-r--r--plugins/decoder/mp3/k3bmaddecoder.plugin9
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