diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-03 02:15:56 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-03 02:15:56 +0000 |
commit | 50b48aec6ddd451a6d1709c0942477b503457663 (patch) | |
tree | a9ece53ec06fd0a2819de7a2a6de997193566626 /libk3b/plugin/k3baudiodecoder.cpp | |
download | k3b-50b48aec6ddd451a6d1709c0942477b503457663.tar.gz k3b-50b48aec6ddd451a6d1709c0942477b503457663.zip |
Added abandoned KDE3 version of K3B
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/k3b@1084400 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'libk3b/plugin/k3baudiodecoder.cpp')
-rw-r--r-- | libk3b/plugin/k3baudiodecoder.cpp | 599 |
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" |