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.
600 lines
15 KiB
600 lines
15 KiB
15 years ago
|
/*
|
||
|
*
|
||
|
* $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"
|