summaryrefslogtreecommitdiffstats
path: root/libk3b/plugin/k3baudiodecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libk3b/plugin/k3baudiodecoder.cpp')
-rw-r--r--libk3b/plugin/k3baudiodecoder.cpp599
1 files changed, 599 insertions, 0 deletions
diff --git a/libk3b/plugin/k3baudiodecoder.cpp b/libk3b/plugin/k3baudiodecoder.cpp
new file mode 100644
index 0000000..82f4adb
--- /dev/null
+++ b/libk3b/plugin/k3baudiodecoder.cpp
@@ -0,0 +1,599 @@
+/*
+ *
+ * $Id: k3baudiodecoder.cpp 619556 2007-01-03 17:38:12Z trueg $
+ * Copyright (C) 2003 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 <config.h>
+
+#include <k3bcore.h>
+
+#include "k3baudiodecoder.h"
+#include "k3bpluginmanager.h"
+
+#include <kdebug.h>
+#include <kfilemetainfo.h>
+
+#include <qmap.h>
+
+#include <math.h>
+
+#ifdef HAVE_LIBSAMPLERATE
+#include <samplerate.h>
+#else
+#include "libsamplerate/samplerate.h"
+#endif
+
+#if !(HAVE_LRINT && HAVE_LRINTF)
+#define lrint(dbl) ((int) (dbl))
+#define lrintf(flt) ((int) (flt))
+#endif
+
+// use a one second buffer
+static const int DECODING_BUFFER_SIZE = 75*2352;
+
+class K3bAudioDecoder::Private
+{
+public:
+ Private()
+ : metaInfo(0),
+ resampleState(0),
+ resampleData(0),
+ inBuffer(0),
+ inBufferPos(0),
+ inBufferFill(0),
+ outBuffer(0),
+ monoBuffer(0),
+ decodingBufferPos(0),
+ decodingBufferFill(0),
+ valid(true) {
+ }
+
+ // the current position of the decoder
+ // This does NOT include the decodingBuffer
+ K3b::Msf currentPos;
+
+ // since the current position above is measured in frames
+ // there might be a little offset since the decoded data is not
+ // always a multiple of 2353 bytes
+ int currentPosOffset;
+
+ // already decoded bytes from last init or last seek
+ // TODO: replace alreadyDecoded with currentPos
+ unsigned long alreadyDecoded;
+
+ K3b::Msf decodingStartPos;
+
+ KFileMetaInfo* metaInfo;
+
+ // set to true once decodeInternal() returned 0
+ bool decoderFinished;
+
+ // resampling
+ SRC_STATE* resampleState;
+ SRC_DATA* resampleData;
+
+ float* inBuffer;
+ float* inBufferPos;
+ int inBufferFill;
+
+ float* outBuffer;
+
+ int samplerate;
+ int channels;
+
+ // mono -> stereo conversion
+ char* monoBuffer;
+
+ char decodingBuffer[DECODING_BUFFER_SIZE];
+ char* decodingBufferPos;
+ int decodingBufferFill;
+
+ QMap<QString, QString> technicalInfoMap;
+ QMap<MetaDataField, QString> metaInfoMap;
+
+ bool valid;
+};
+
+
+
+K3bAudioDecoder::K3bAudioDecoder( QObject* parent, const char* name )
+ : QObject( parent, name )
+{
+ d = new Private();
+}
+
+
+K3bAudioDecoder::~K3bAudioDecoder()
+{
+ cleanup();
+
+ if( d->inBuffer ) delete [] d->inBuffer;
+ if( d->outBuffer ) delete [] d->outBuffer;
+ if( d->monoBuffer ) delete [] d->monoBuffer;
+
+ delete d->metaInfo;
+ delete d->resampleData;
+ if( d->resampleState )
+ src_delete( d->resampleState );
+ delete d;
+}
+
+
+void K3bAudioDecoder::setFilename( const QString& filename )
+{
+ m_fileName = filename;
+ delete d->metaInfo;
+ d->metaInfo = 0;
+}
+
+
+bool K3bAudioDecoder::isValid() const
+{
+ return d->valid;
+}
+
+
+bool K3bAudioDecoder::analyseFile()
+{
+ d->technicalInfoMap.clear();
+ d->metaInfoMap.clear();
+ delete d->metaInfo;
+ d->metaInfo = 0;
+
+ cleanup();
+
+ bool ret = analyseFileInternal( m_length, d->samplerate, d->channels );
+ if( ret && ( d->channels == 1 || d->channels == 2 ) && m_length > 0 ) {
+ d->valid = initDecoder();
+ return d->valid;
+ }
+ else {
+ d->valid = false;
+ return false;
+ }
+}
+
+
+bool K3bAudioDecoder::initDecoder( const K3b::Msf& startOffset )
+{
+ if( initDecoder() ) {
+ if( startOffset > 0 )
+ return seek( startOffset );
+ else
+ return true;
+ }
+ else
+ return false;
+}
+
+
+bool K3bAudioDecoder::initDecoder()
+{
+ cleanup();
+
+ if( d->resampleState )
+ src_reset( d->resampleState );
+
+ d->alreadyDecoded = 0;
+ d->currentPos = 0;
+ d->currentPosOffset = 0;
+ d->decodingBufferFill = 0;
+ d->decodingBufferPos = 0;
+ d->decodingStartPos = 0;
+ d->inBufferFill = 0;
+
+ d->decoderFinished = false;
+
+ return initDecoderInternal();
+}
+
+
+int K3bAudioDecoder::decode( char* _data, int maxLen )
+{
+ unsigned long lengthToDecode = (m_length - d->decodingStartPos).audioBytes();
+
+ if( d->alreadyDecoded >= lengthToDecode )
+ return 0;
+
+ if( maxLen <= 0 )
+ return 0;
+
+ int read = 0;
+
+ if( d->decodingBufferFill == 0 ) {
+ //
+ // now we decode into the decoding buffer
+ // to ensure a minimum buffer size
+ //
+ d->decodingBufferFill = 0;
+ d->decodingBufferPos = d->decodingBuffer;
+
+ if( !d->decoderFinished ) {
+ if( d->samplerate != 44100 ) {
+
+ // check if we have data left from some previous conversion
+ if( d->inBufferFill > 0 ) {
+ read = resample( d->decodingBuffer, DECODING_BUFFER_SIZE );
+ }
+ else {
+ if( !d->inBuffer ) {
+ d->inBuffer = new float[DECODING_BUFFER_SIZE/2];
+ }
+
+ if( (read = decodeInternal( d->decodingBuffer, DECODING_BUFFER_SIZE )) == 0 )
+ d->decoderFinished = true;
+
+ d->inBufferFill = read/2;
+ d->inBufferPos = d->inBuffer;
+ from16bitBeSignedToFloat( d->decodingBuffer, d->inBuffer, d->inBufferFill );
+
+ read = resample( d->decodingBuffer, DECODING_BUFFER_SIZE );
+ }
+ }
+ else if( d->channels == 1 ) {
+ if( !d->monoBuffer ) {
+ d->monoBuffer = new char[DECODING_BUFFER_SIZE/2];
+ }
+
+ // we simply duplicate every frame
+ if( (read = decodeInternal( d->monoBuffer, DECODING_BUFFER_SIZE/2 )) == 0 )
+ d->decoderFinished = true;
+
+ for( int i = 0; i < read; i+=2 ) {
+ d->decodingBuffer[2*i] = d->decodingBuffer[2*i+2] = d->monoBuffer[i];
+ d->decodingBuffer[2*i+1] = d->decodingBuffer[2*i+3] = d->monoBuffer[i+1];
+ }
+
+ read *= 2;
+ }
+ else {
+ if( (read = decodeInternal( d->decodingBuffer, DECODING_BUFFER_SIZE )) == 0 )
+ d->decoderFinished = true;
+ }
+ }
+
+ if( read < 0 ) {
+ return -1;
+ }
+ else if( read == 0 ) {
+ // check if we need to pad
+ int bytesToPad = lengthToDecode - d->alreadyDecoded;
+ if( bytesToPad > 0 ) {
+ kdDebug() << "(K3bAudioDecoder) track length: " << lengthToDecode
+ << "; decoded module data: " << d->alreadyDecoded
+ << "; we need to pad " << bytesToPad << " bytes." << endl;
+
+ if( DECODING_BUFFER_SIZE < bytesToPad )
+ bytesToPad = DECODING_BUFFER_SIZE;
+
+ ::memset( d->decodingBuffer, 0, bytesToPad );
+
+ kdDebug() << "(K3bAudioDecoder) padded " << bytesToPad << " bytes." << endl;
+
+ read = bytesToPad;
+ }
+ else {
+ kdDebug() << "(K3bAudioDecoder) decoded " << d->alreadyDecoded << " bytes." << endl;
+ return 0;
+ }
+ }
+ else {
+
+ // check if we decoded too much
+ if( d->alreadyDecoded + read > lengthToDecode ) {
+ kdDebug() << "(K3bAudioDecoder) we decoded too much. Cutting output by "
+ << (read + d->alreadyDecoded - lengthToDecode) << endl;
+ read = lengthToDecode - d->alreadyDecoded;
+ }
+ }
+
+ d->decodingBufferFill = read;
+ }
+
+
+ // clear out the decoding buffer
+ read = QMIN( maxLen, d->decodingBufferFill );
+ ::memcpy( _data, d->decodingBufferPos, read );
+ d->decodingBufferPos += read;
+ d->decodingBufferFill -= read;
+
+ d->alreadyDecoded += read;
+ d->currentPos += (read+d->currentPosOffset)/2352;
+ d->currentPosOffset = (read+d->currentPosOffset)%2352;
+
+ return read;
+}
+
+
+// resample data in d->inBufferPos and save the result to data
+//
+//
+int K3bAudioDecoder::resample( char* data, int maxLen )
+{
+ if( !d->resampleState ) {
+ d->resampleState = src_new( SRC_SINC_MEDIUM_QUALITY, d->channels, 0 );
+ if( !d->resampleState ) {
+ kdDebug() << "(K3bAudioDecoder) unable to initialize resampler." << endl;
+ return -1;
+ }
+ d->resampleData = new SRC_DATA;
+ }
+
+ if( !d->outBuffer ) {
+ d->outBuffer = new float[DECODING_BUFFER_SIZE/2];
+ }
+
+ d->resampleData->data_in = d->inBufferPos;
+ d->resampleData->data_out = d->outBuffer;
+ d->resampleData->input_frames = d->inBufferFill/d->channels;
+ d->resampleData->output_frames = maxLen/2/2; // in case of mono files we need the space anyway
+ d->resampleData->src_ratio = 44100.0/(double)d->samplerate;
+ if( d->inBufferFill == 0 )
+ d->resampleData->end_of_input = 1; // this should force libsamplerate to output the last frames
+ else
+ d->resampleData->end_of_input = 0;
+
+ int len = 0;
+ if( (len = src_process( d->resampleState, d->resampleData ) ) ) {
+ kdDebug() << "(K3bAudioDecoder) error while resampling: " << src_strerror(len) << endl;
+ return -1;
+ }
+
+ if( d->channels == 2 )
+ fromFloatTo16BitBeSigned( d->outBuffer, data, d->resampleData->output_frames_gen*d->channels );
+ else {
+ for( int i = 0; i < d->resampleData->output_frames_gen; ++i ) {
+ fromFloatTo16BitBeSigned( &d->outBuffer[i], &data[4*i], 1 );
+ fromFloatTo16BitBeSigned( &d->outBuffer[i], &data[4*i+2], 1 );
+ }
+ }
+
+ d->inBufferPos += d->resampleData->input_frames_used*d->channels;
+ d->inBufferFill -= d->resampleData->input_frames_used*d->channels;
+ if( d->inBufferFill <= 0 ) {
+ d->inBufferPos = d->inBuffer;
+ d->inBufferFill = 0;
+ }
+
+ // 16 bit frames, so we need to multiply by 2
+ // and we always have two channels
+ return d->resampleData->output_frames_gen*2*2;
+}
+
+
+void K3bAudioDecoder::from16bitBeSignedToFloat( char* src, float* dest, int samples )
+{
+ while( samples ) {
+ samples--;
+ dest[samples] = static_cast<float>( Q_INT16(((src[2*samples]<<8)&0xff00)|(src[2*samples+1]&0x00ff)) / 32768.0 );
+ }
+}
+
+
+void K3bAudioDecoder::fromFloatTo16BitBeSigned( float* src, char* dest, int samples )
+{
+ while( samples ) {
+ samples--;
+
+ float scaled = src[samples] * 32768.0;
+ Q_INT16 val = 0;
+
+ // clipping
+ if( scaled >= ( 1.0 * 0x7FFF ) )
+ val = 32767;
+ else if( scaled <= ( -8.0 * 0x1000 ) )
+ val = -32768;
+ else
+ val = lrintf(scaled);
+
+ dest[2*samples] = val>>8;
+ dest[2*samples+1] = val;
+ }
+}
+
+
+void K3bAudioDecoder::from8BitTo16BitBeSigned( char* src, char* dest, int samples )
+{
+ while( samples ) {
+ samples--;
+
+ float scaled = static_cast<float>(Q_UINT8(src[samples])-128) / 128.0 * 32768.0;
+ Q_INT16 val = 0;
+
+ // clipping
+ if( scaled >= ( 1.0 * 0x7FFF ) )
+ val = 32767;
+ else if( scaled <= ( -8.0 * 0x1000 ) )
+ val = -32768;
+ else
+ val = lrintf(scaled);
+
+ dest[2*samples] = val>>8;
+ dest[2*samples+1] = val;
+ }
+}
+
+
+bool K3bAudioDecoder::seek( const K3b::Msf& pos )
+{
+ kdDebug() << "(K3bAudioDecoder) seek from " << d->currentPos.toString() << " (+" << d->currentPosOffset
+ << ") to " << pos.toString() << endl;
+
+ if( pos > length() )
+ return false;
+
+ d->decoderFinished = false;
+
+ if( pos == d->currentPos && d->currentPosOffset == 0 )
+ return true;
+
+ if( pos == 0 )
+ return initDecoder();
+
+ bool success = false;
+
+ //
+ // First check if we may do a "perfect seek".
+ // We cannot rely on the decoding plugins to seek perfectly. Especially
+ // the mp3 decoder does not. But in case we want to split a live recording
+ // it is absolutely nesseccary to perform a perfect seek.
+ // So if we did not already decode past the seek position and the difference
+ // between the current position and the seek position is less than some fixed
+ // value we simply decode up to the seek position.
+ //
+ if( ( pos > d->currentPos ||
+ ( pos == d->currentPos && d->currentPosOffset == 0 ) )
+ &&
+ ( pos - d->currentPos < K3b::Msf(0,10,0) ) ) { // < 10 seconds is ok
+ kdDebug() << "(K3bAudioDecoder) performing perfect seek from " << d->currentPos.toString()
+ << " to " << pos.toString() << ". :)" << endl;
+
+ unsigned long bytesToDecode = pos.audioBytes() - d->currentPos.audioBytes() - d->currentPosOffset;
+ kdDebug() << "(K3bAudioDecoder) seeking " << bytesToDecode << " bytes." << endl;
+ char buffi[10*2352];
+ while( bytesToDecode > 0 ) {
+ int read = decode( buffi, QMIN(10*2352, bytesToDecode) );
+ if( read <= 0 )
+ return false;
+
+ bytesToDecode -= read;
+ }
+
+ kdDebug() << "(K3bAudioDecoder) perfect seek done." << endl;
+
+ success = true;
+ }
+ else {
+ //
+ // Here we have to reset the resampling stuff since we restart decoding at another position.
+ //
+ if( d->resampleState )
+ src_reset( d->resampleState );
+ d->inBufferFill = 0;
+
+ //
+ // And also reset the decoding buffer to not return any garbage from previous decoding.
+ //
+ d->decodingBufferFill = 0;
+
+ success = seekInternal( pos );
+ }
+
+ d->alreadyDecoded = 0;
+ d->currentPos = d->decodingStartPos = pos;
+ d->currentPosOffset = 0;
+
+ return success;
+}
+
+
+void K3bAudioDecoder::cleanup()
+{
+}
+
+
+QString K3bAudioDecoder::metaInfo( MetaDataField f )
+{
+ if( d->metaInfoMap.contains( f ) )
+ return d->metaInfoMap[f];
+
+ // fall back to KFileMetaInfo
+ if( !d->metaInfo )
+ d->metaInfo = new KFileMetaInfo( filename() );
+
+ if( d->metaInfo->isValid() ) {
+ QString tag;
+ switch( f ) {
+ case META_TITLE:
+ tag = "Title";
+ break;
+ case META_ARTIST:
+ tag = "Artist";
+ break;
+ case META_SONGWRITER:
+ tag = "Songwriter";
+ break;
+ case META_COMPOSER:
+ tag = "Composer";
+ break;
+ case META_COMMENT:
+ tag = "Comment";
+ break;
+ }
+
+ KFileMetaInfoItem item = d->metaInfo->item( tag );
+ if( item.isValid() )
+ return item.string();
+ }
+
+ return QString::null;
+}
+
+
+void K3bAudioDecoder::addMetaInfo( MetaDataField f, const QString& value )
+{
+ if( !value.isEmpty() )
+ d->metaInfoMap[f] = value;
+ else
+ kdDebug() << "(K3bAudioDecoder) empty meta data field." << endl;
+}
+
+
+QStringList K3bAudioDecoder::supportedTechnicalInfos() const
+{
+ QStringList l;
+ for( QMap<QString, QString>::const_iterator it = d->technicalInfoMap.begin();
+ it != d->technicalInfoMap.end(); ++it )
+ l.append( it.key() );
+ return l;
+}
+
+
+QString K3bAudioDecoder::technicalInfo( const QString& key ) const
+{
+ return d->technicalInfoMap[key];
+}
+
+
+void K3bAudioDecoder::addTechnicalInfo( const QString& key, const QString& value )
+{
+ d->technicalInfoMap[key] = value;
+}
+
+
+K3bAudioDecoder* K3bAudioDecoderFactory::createDecoder( const KURL& url )
+{
+ kdDebug() << "(K3bAudioDecoderFactory::createDecoder( " << url.path() << " )" << endl;
+ QPtrList<K3bPlugin> fl = k3bcore->pluginManager()->plugins( "AudioDecoder" );
+
+ // first search for a single format decoder
+ for( QPtrListIterator<K3bPlugin> it( fl ); it.current(); ++it ) {
+ K3bAudioDecoderFactory* f = dynamic_cast<K3bAudioDecoderFactory*>( it.current() );
+ if( f && !f->multiFormatDecoder() && f->canDecode( url ) ) {
+ kdDebug() << "1" << endl; return f->createDecoder();}
+ }
+
+ // no single format decoder. Search for a multi format decoder
+ for( QPtrListIterator<K3bPlugin> it( fl ); it.current(); ++it ) {
+ K3bAudioDecoderFactory* f = dynamic_cast<K3bAudioDecoderFactory*>( it.current() );
+ if( f && f->multiFormatDecoder() && f->canDecode( url ) ) {
+ kdDebug() << "2" << endl; return f->createDecoder();}
+ }
+
+ kdDebug() << "(K3bAudioDecoderFactory::createDecoder( " << url.path() << " ) no success" << endl;
+
+ // nothing found
+ return 0;
+}
+
+#include "k3baudiodecoder.moc"