diff --git a/src/Experimental.h b/src/Experimental.h index da1e3f102..45e783aa1 100644 --- a/src/Experimental.h +++ b/src/Experimental.h @@ -83,6 +83,8 @@ // have not been fully imported in builds without FLAC support, so disabled for // 2.0 release //#define EXPERIMENTAL_OD_FLAC +// similarly for FFmpeg: +//#define EXPERIMENTAL_OD_FFMPEG // Philip Van Baren 01 July 2009 // Replace RealFFT() and PowerSpectrum function to use (faster) RealFFTf function diff --git a/src/FFmpeg.cpp b/src/FFmpeg.cpp index f16bc1a4c..974f610fb 100644 --- a/src/FFmpeg.cpp +++ b/src/FFmpeg.cpp @@ -153,7 +153,15 @@ void av_log_wx_callback(void* ptr, int level, const char* fmt, va_list vl) case 2: cpt = wxT("Debug"); break; default: cpt = wxT("Log"); break; } - wxLogMessage(wxT("%s: %s"),cpt.c_str(),printstring.c_str()); +#ifdef EXPERIMENTAL_OD_FFMPEG +//if the decoding happens thru OD then this gets called from a non main thread, which means wxLogMessage +//will crash. +//TODO:find some workaround for the log. perhaps use ODManager as a bridge. for now just print + if(!wxThread::IsMain()) + printf("%s: %s\n",cpt.c_str(),printstring.c_str()); + else +#endif + wxLogMessage(wxT("%s: %s"),cpt.c_str(),printstring.c_str()); } //======================= Unicode aware uri protocol for FFmpeg diff --git a/src/FFmpeg.h b/src/FFmpeg.h index bee15b3b2..769bd04a5 100644 --- a/src/FFmpeg.h +++ b/src/FFmpeg.h @@ -57,6 +57,8 @@ extern "C" { #include #include +#include "Experimental.h" + // if you needed them, any other audacity header files would go here /* These defines apply whether or not ffmpeg is available */ @@ -397,6 +399,30 @@ void DropFFmpegLibs(); int ufile_fopen(ByteIOContext **s, const wxString & name, int flags); int ufile_fopen_input(AVFormatContext **ic_ptr, wxString & name); +//moving from ImportFFmpeg.cpp to FFMpeg.h so other cpp files can use this struct. +#ifdef EXPERIMENTAL_OD_FFMPEG +typedef struct _streamContext +{ + bool m_use; // TRUE = this stream will be loaded into Audacity + AVStream *m_stream; // an AVStream * + AVCodecContext *m_codecCtx; // pointer to m_stream->codec + + AVPacket m_pkt; // the last AVPacket we read for this stream + int m_pktValid; // is m_pkt valid? + uint8_t *m_pktDataPtr; // pointer into m_pkt.data + int m_pktRemainingSiz; + + int64_t m_pts; // the current presentation time of the input stream + int64_t m_ptsOffset; // packets associated with stream are relative to this + + int m_frameValid; // is m_decodedVideoFrame/m_decodedAudioSamples valid? + int16_t *m_decodedAudioSamples; // decoded audio samples stored here + unsigned int m_decodedAudioSamplesSiz; // current size of m_decodedAudioSamples + int m_decodedAudioSamplesValidSiz; // # valid bytes in m_decodedAudioSamples + int m_initialchannels; // number of channels allocated when we begin the importing. Assumes that number of channels doesn't change on the fly. +} streamContext; +#endif + #endif // USE_FFMPEG #endif // __AUDACITY_FFMPEG__ diff --git a/src/import/ImportFFmpeg.cpp b/src/import/ImportFFmpeg.cpp index cf716096d..8e325ef83 100644 --- a/src/import/ImportFFmpeg.cpp +++ b/src/import/ImportFFmpeg.cpp @@ -25,6 +25,7 @@ Licensed under the GNU General Public License v2 or later #include "../Audacity.h" // needed before FFmpeg.h #include "../FFmpeg.h" // which brings in avcodec.h, avformat.h +#include "../ondemand/ODManager.h" #ifndef WX_PRECOMP // Include your minimal set of headers here, or wx.h #include @@ -153,10 +154,16 @@ static const wxChar *exts[] = #include "../WaveTrack.h" #include "ImportPlugin.h" + +#ifdef EXPERIMENTAL_OD_FFMPEG +#include "ODDecodeFFMpegTask.h" +#endif + extern FFmpegLibs *FFmpegLibsInst; class FFmpegImportFileHandle; - +//moving from ImportFFmpeg.cpp to FFMpeg.h so other cpp files can use this struct. +#ifndef EXPERIMENTAL_OD_FFMPEG typedef struct _streamContext { bool m_use; // TRUE = this stream will be loaded into Audacity @@ -177,7 +184,7 @@ typedef struct _streamContext int m_decodedAudioSamplesValidSiz; // # valid bytes in m_decodedAudioSamples int m_initialchannels; // number of channels allocated when we begin the importing. Assumes that number of channels doesn't change on the fly. } streamContext; - +#endif /// A representative of FFmpeg loader in /// the Audacity import plugin list class FFmpegImportPlugin : public ImportPlugin @@ -278,6 +285,10 @@ private: bool mStopped; //!< True if importing was stopped by user wxString mName; WaveTrack ***mChannels; //!< 2-dimentional array of WaveTrack's. First dimention - streams, second - channels of a stream. Length is mNumStreams +#ifdef EXPERIMENTAL_OD_FFMPEG + bool mUsingOD; +#endif + }; @@ -533,11 +544,68 @@ int FFmpegImportFileHandle::Import(TrackFactory *trackFactory, } } } - // This is the heart of the importing process streamContext *sc = NULL; // The result of Import() to be returend. It will be something other than zero if user canceled or some error appears. int res = eProgressSuccess; + +#ifdef EXPERIMENTAL_OD_FFMPEG + mUsingOD = true; //TODO: for now just use it - later use a prefs + //at this point we know the file is good and that we have to load the number of channels in mScs[s]->m_stream->codec->channels; + //so for OD loading we create the tracks and releasee the modal lock after starting the ODTask. + std::vector tasks; + //append blockfiles to each stream and add an individual ODDecodeTask for each one. + for (int s = 0; s < mNumStreams; s++) + { + ODDecodeFFmpegTask* odTask=new ODDecodeFFmpegTask(mScs,mNumStreams,mChannels,mFormatContext); + ODFileDecoder* odDecoder = (ODFileDecoder*)odTask->CreateFileDecoder(mFilename); + + //each stream has different duration. We need to know it if seeking is to be allowed. + sampleCount sampleDuration = 0; + if (mScs[s]->m_stream->duration > 0) + sampleDuration = ((sampleCount)mScs[s]->m_stream->duration * mScs[s]->m_stream->time_base.num) *mScs[s]->m_stream->codec->sample_rate / mScs[s]->m_stream->time_base.den; + else + sampleDuration = ((sampleCount)mFormatContext->duration *mScs[s]->m_stream->codec->sample_rate) / AV_TIME_BASE; + +// printf(" OD duration samples %qi, sr %d, secs %d\n",sampleDuration, (int)mScs[s]->m_stream->codec->sample_rate,(int)sampleDuration/mScs[s]->m_stream->codec->sample_rate); + + //for each wavetrack within the stream add coded blockfiles + for (int c = 0; c < mScs[s]->m_stream->codec->channels; c++) + { + WaveTrack *t = mChannels[s][c]; + odTask->AddWaveTrack(t); + + sampleCount maxBlockSize = t->GetMaxBlockSize(); + //use the maximum blockfile size to divide the sections (about 11secs per blockfile at 44.1khz) + for (sampleCount i = 0; i < sampleDuration; i += maxBlockSize) + { + sampleCount blockLen = maxBlockSize; + if (i + blockLen > sampleDuration) + blockLen = sampleDuration - i; + + t->AppendCoded(mFilename, i, blockLen, c,ODTask::eODFFMPEG); + + // This only works well for single streams since we assume + // each stream is of the same duration and channels + res = mProgress->Update(i+sampleDuration*c+ sampleDuration*mScs[s]->m_stream->codec->channels*s, + sampleDuration*mScs[s]->m_stream->codec->channels*mNumStreams); + if (res != eProgressSuccess) + break; + } + } + tasks.push_back(odTask); + } + //Now we add the tasks and let them run, or delete them if the user cancelled + for(int i=0;iAddNewTask(tasks[i]); + else + { + delete tasks[i]; + } + } +#else //ifndef EXPERIMENTAL_OD_FFMPEG // Read next frame. while ((sc = ReadNextFrame()) != NULL && (res == eProgressSuccess)) { @@ -589,6 +657,7 @@ int FFmpegImportFileHandle::Import(TrackFactory *trackFactory, } } } +#endif //EXPERIMENTAL_OD_FFMPEG // Something bad happened - destroy everything! if (res == eProgressCancelled || res == eProgressFailed) @@ -836,6 +905,11 @@ void FFmpegImportFileHandle::WriteMetadata(AVFormatContext *avf,Tags *tags) FFmpegImportFileHandle::~FFmpegImportFileHandle() { +#ifdef EXPERIMENTAL_OD_FFMPEG + //ODDecodeFFmpegTask takes ownership and deltes it there. + if(!mUsingOD) + { +#endif if (FFmpegLibsInst->ValidLibsLoaded()) { if (mFormatContext) FFmpegLibsInst->av_close_input_file(mFormatContext); @@ -850,6 +924,10 @@ FFmpegImportFileHandle::~FFmpegImportFileHandle() delete mScs[i]; } free(mScs); +#ifdef EXPERIMENTAL_OD_FFMPEG + }//mUsingOD +#endif + delete mStreamInfo; diff --git a/src/ondemand/ODDecodeFFmpegTask.cpp b/src/ondemand/ODDecodeFFmpegTask.cpp new file mode 100644 index 000000000..accba7c19 --- /dev/null +++ b/src/ondemand/ODDecodeFFmpegTask.cpp @@ -0,0 +1,569 @@ +/* + * ODDecodeFFmpegTask.cpp + * Audacity + * + * Created by apple on 3/8/10. + * Copyright 2010 __MyCompanyName__. All rights reserved. + * + */ +#include + #include "../Experimental.h" +// For compilers that support precompilation, includes "wx/wx.h". +#ifdef EXPERIMENTAL_OD_FFMPEG + + +#include "../Audacity.h" // needed before FFmpeg.h +#include "../FFmpeg.h" // which brings in avcodec.h, avformat.h +#ifndef WX_PRECOMP +// Include your minimal set of headers here, or wx.h +#include +#endif + + +extern FFmpegLibs *FFmpegLibsInst; +#include "ODDecodeFFmpegTask.h" + +#define kMaxSamplesInCache 4410000 + +//struct for caching the decoded samples to be used over multiple blockfiles +typedef struct _FFmpegDecodeCache +{ + int16_t* samplePtr;//interleaved samples - currently ffmpeg only uses 16 bit int + sampleCount start; + sampleCount len; + int numChannels; +} FFMpegDecodeCache; + + +//------ ODFFmpegDecoder declaration and defs - here because we strip dependencies from .h files + +///class to decode a particular file (one per file). Saves info such as filename and length (after the header is read.) +class ODFFmpegDecoder:public ODFileDecoder +{ + + +public: + ///This should handle unicode converted to UTF-8 on mac/linux, but OD TODO:check on windows + ODFFmpegDecoder(const wxString & fileName, streamContext** scs, int numStreams,WaveTrack*** channels, AVFormatContext* formatContext); + virtual ~ODFFmpegDecoder(); + + ///Decodes the samples for this blockfile from the real file into a float buffer. + ///This is file specific, so subclasses must implement this only. + ///the buffer was defined like + ///samplePtr sampleData = NewSamples(mLen, floatSample); + ///this->ReadData(sampleData, floatSample, 0, mLen); + ///This class should call ReadHeader() first, so it knows the length, and can prepare + ///the file object if it needs to. + virtual void Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel); + + ///This is a must implement abstract virtual in the superclass. + ///However it doesn't do anything because ImportFFMpeg does all that for us. + virtual bool ReadHeader(){} + +private: + int FillDataFromCache(samplePtr & data, sampleCount start, sampleCount& len, unsigned int channel); + ///REFACTORABLE CODE FROM IMPORT FFMPEG + ///! Reads next audio frame + ///\return pointer to the stream context structure to which the frame belongs to or NULL on error, or 1 if stream is not to be imported. + streamContext* ReadNextFrame(); + + ///! Decodes the frame + ///\param sc - stream context (from ReadNextFrame) + ///\param flushing - true if flushing (no more frames left), false otherwise + ///\return 0 on success, -1 if it can't decode any further + int DecodeFrame(streamContext *sc, bool flushing); + + ///! Writes decoded data into WaveTracks. Called by DecodeFrame + ///\param sc - stream context + ///\return 0 on success, 1 on error or interruption + // also, updates the data and len inputs to reflect the result. + int WriteData(streamContext *sc,samplePtr & data, sampleCount& len, unsigned int channel); + + int mNumStreams; + streamContext **mScs; //!< Array of pointers to stream contexts. Length is mNumStreams. + WaveTrack*** mChannels; + AVFormatContext *mFormatContext; //!< Format description, also contains metadata and some useful info + std::vector mDecodeCache; + int mNumSamplesInCache; + int mCurrentPos; //the index of the next sample to be decoded +}; + + +//------ ODDecodeFFmpegTask definitions +ODDecodeFFmpegTask::ODDecodeFFmpegTask(void* scs,int numStreams, WaveTrack*** channels, void* formatContext) +{ + mScs=scs; + mNumStreams=numStreams; + mChannels=channels; + mFormatContext = formatContext; + //TODO we probably need to create a new WaveTrack*** pointer and copy. + //same for streamContext, but we should also use a ref counting system - this should be added to streamContext + // mScs = (streamContext**)malloc(sizeof(streamContext**)*mFormatContext->nb_streams); +} +ODDecodeFFmpegTask::~ODDecodeFFmpegTask() +{ +} + + +ODTask* ODDecodeFFmpegTask::Clone() +{ + //we need to create copies of mScs. It would be better to use a reference counter system. + + ODDecodeFFmpegTask* clone = new ODDecodeFFmpegTask((void*)mScs,mNumStreams,mChannels,mFormatContext); + clone->mDemandSample=GetDemandSample(); + + //the decoders and blockfiles should not be copied. They are created as the task runs. + return clone; +} + +///Creates an ODFileDecoder that decodes a file of filetype the subclass handles. +// +//compare to FLACImportPlugin::Open(wxString filename) +ODFileDecoder* ODDecodeFFmpegTask::CreateFileDecoder(const wxString & fileName) +{ + // Open the file for import + ODFFmpegDecoder *decoder = new ODFFmpegDecoder(fileName, (streamContext**) mScs,mNumStreams,mChannels,(AVFormatContext*)mFormatContext); + + mDecoders.push_back(decoder); + return decoder; + +} + +/// subclasses need to override this if they cannot always seek. +/// seeking will be enabled once this is true. +bool ODDecodeFFmpegTask::SeekingAllowed() +{ + //TODO: find out when we can seek - we can make an educated guess if the first 5% of the frames were of fixed size. + return false; +} + + +//------ ODDecodeFFmpegFileDecoder +ODFFmpegDecoder::ODFFmpegDecoder(const wxString & fileName, streamContext** scs,int numStreams,WaveTrack*** channels, AVFormatContext* formatContext) +:ODFileDecoder(fileName), +//mSamplesDone(0), +mNumStreams(numStreams), +mScs(scs), +mFormatContext(formatContext), +mCurrentPos(0), +mNumSamplesInCache(0) +{ + PickFFmpegLibs(); + + //do a shallow copy of the 2d array. + mChannels = new WaveTrack **[mNumStreams]; + + for (int s = 0; s < mNumStreams; s++) + { + mChannels[s] = new WaveTrack *[mScs[s]->m_stream->codec->channels]; + int c; + for (c = 0; c < mScs[s]->m_stream->codec->channels; c++) + { + mChannels[s][c] = channels[s][c]; + } + } + + //TODO: add a ref counter to scs? This will be necessary if we want to allow copy and paste of not-yet decoded + //ODDecodeBlockFiles that point to FFmpeg files. +} + +//we have taken ownership, so delete the ffmpeg stuff allocated in ImportFFmpeg that was given to us. +ODFFmpegDecoder::~ODFFmpegDecoder() +{ + if (FFmpegLibsInst->ValidLibsLoaded()) + { + if (mFormatContext) FFmpegLibsInst->av_close_input_file(mFormatContext); + FFmpegLibsInst->av_log_set_callback(FFmpegLibsInst->av_log_default_callback); + } + + for (int i = 0; i < mNumStreams; i++) + { + if (mScs[i]->m_decodedAudioSamples != NULL) + FFmpegLibsInst->av_free(mScs[i]->m_decodedAudioSamples); + + delete mScs[i]; + } + free(mScs); + + //delete our caches. + while(mDecodeCache.size()) + { + free(mDecodeCache[0]->samplePtr); + delete mDecodeCache[0]; + mDecodeCache.erase(mDecodeCache.begin()); + } + + //free the channel pointer arrays + for (int s = 0; s < mNumStreams; s++) + { + delete[] mChannels[s]; + } + delete[] mChannels; + DropFFmpegLibs(); +} + +void ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel) +{ + + //it looks like the code in importFFmpeg.cpp only imports 16 bit int - need to see why. + format = int16Sample; + + data = NewSamples(len, format); + samplePtr bufStart = data; + streamContext* sc = NULL; + + sampleCount origLen=len; + + int nChannels; + + //TODO update this to work with seek - this only works linearly now. + if(mCurrentPos > start) + { + //this next call takes data, start and len as reference variables and updates them to reflect the new area that is needed. + FillDataFromCache(bufStart,start,len,channel); + } + +// mDecodeBuffer=data; + while (len>0 && (sc = ReadNextFrame()) != NULL) + { + // ReadNextFrame returns 1 if stream is not to be imported + if (sc != (streamContext*)1) + { + nChannels = sc->m_stream->codec->channels < sc->m_initialchannels ? sc->m_stream->codec->channels : sc->m_initialchannels; + + //decode the entire packet (unused bits get saved in cache, so as long as cache size limit is bigger than the + //largest packet size, we're ok. + while (sc->m_pktRemainingSiz > 0)// && (res == eProgressSuccess || res == eProgressStopped)) + { + //if(mCurrentPos < start+origLen) + if (DecodeFrame(sc,false) < 0) + break; + + // If something useable was decoded - write it to the buffer + //note that bufStart and len are updated by the WriteData function. + if (sc->m_frameValid && len > 0) + WriteData(sc,bufStart,len,channel); + } + + // Cleanup after frame decoding + if (sc->m_pktValid) + { +#if FFMPEG_STABLE + av_free_packet(&sc->m_pkt); +#else + FFmpegLibsInst->av_free_packet(&sc->m_pkt); +#endif + sc->m_pktValid = 0; + } + } + } + + + // Flush the decoders if we're done. + if(!sc && len>0) + { + for (int i = 0; i < mNumStreams; i++) + { + if (DecodeFrame(mScs[i], true) == 0) + { + WriteData(mScs[i],bufStart,len,channel); + + if (mScs[i]->m_pktValid) + { +#if FFMPEG_STABLE + av_free_packet(&mScs[i]->m_pkt); +#else + FFmpegLibsInst->av_free_packet(&mScs[i]->m_pkt); +#endif + mScs[i]->m_pktValid = 0; + } + } + } + } + + + + //if for some reason we couldn't get the samples, fill them with silence + int16_t* outBuf = (int16_t*) bufStart; + for(int i=0;ikODFFmpegSearchThreshold) + { + //first just guess that the cache is contiguous and we can just use math to figure it out like a dictionary + //by guessing where our hit will be. + while(searchStart+1start )/mDecodeCache[searchEnd]->start; + if(guess == searchEnd) + guess--; //scoot on down to guarantee at max a final condition with spacing of one + else if(guess == searchStart) + guess = (searchStart+searchEnd)/2;//find a midpoint. + + if(mDecodeCache[guess]->start>start) + searchEnd = guess; + else + searchStart = guess; + } + } + + //most recent caches are at the end of the vector, so start there. + for(int i=searchStart;istart+mDecodeCache[i]->len && + start+len > mDecodeCache[i]->start) + { + //ffmpeg only uses 16 bit ints + int16_t* outBuf; + outBuf = (int16_t*)data; + //for debug + FFMpegDecodeCache* cache = mDecodeCache[i]; + //reject buffers that would split us into two pieces because we don't have + //a method of dealing with this yet, and it won't happen very often. + if(startstart && start+len > mDecodeCache[i]->start+mDecodeCache[i]->len) + continue; + + + int samplesHit; + int hitStartInCache; + int hitStartInRequest; + int nChannels = mDecodeCache[i]->numChannels; + samplesHit = FFMIN(start+len,mDecodeCache[i]->start+mDecodeCache[i]->len) + - FFMAX(mDecodeCache[i]->start,start); + //find the start of the hit relative to the cache buffer start. + hitStartInCache = FFMAX(0,start-mDecodeCache[i]->start); + //we also need to find out which end was hit - if it is the tail only we need to update from a later index. + hitStartInRequest = start start?len - samplesHit: 0; + for(int j=0;jsamplePtr[(hitStartInCache+j)*nChannels+channel]; + } + //update the cursor + samplesFilled += samplesHit; + + //update the input start/len params - if the end was hit we can take off just len. + //otherwise, we can assume only the front of the request buffer was hit since we don't allow it to be split. + if(start < mDecodeCache[i]->start) + len-=samplesHit; + else + { + //we update data pointer too- but it is a typedef'd char* so be careful with the pointer math + data+= samplesHit* (sizeof(int16_t)/sizeof(*data)); + start+=samplesHit; + len -=samplesHit; + } + } + //if we've had our fill, leave + if(len<=0) + break; + } + return samplesFilled; +} + + +//these next few look highly refactorable. +//get the right stream pointer. +streamContext* ODFFmpegDecoder::ReadNextFrame() +{ + streamContext *sc = NULL; + AVPacket pkt; + + if (FFmpegLibsInst->av_read_frame(mFormatContext,&pkt) < 0) + { + return NULL; + } + + // Find a stream to which this frame belongs to + for (int i = 0; i < mNumStreams; i++) + { + if (mScs[i]->m_stream->index == pkt.stream_index) + sc = mScs[i]; + } + + // Off-stream packet. Don't panic, just skip it. + // When not all streams are selected for import this will happen very often. + if (sc == NULL) + { +#if FFMPEG_STABLE + av_free_packet(&pkt); +#else + FFmpegLibsInst->av_free_packet(&pkt); +#endif + return (streamContext*)1; + } + + // Copy the frame to the stream context + memcpy(&sc->m_pkt, &pkt, sizeof(AVPacket)); + + sc->m_pktValid = 1; + sc->m_pktDataPtr = pkt.data; + sc->m_pktRemainingSiz = pkt.size; + + return sc; +} + + +int ODFFmpegDecoder::DecodeFrame(streamContext *sc, bool flushing) +{ + int nBytesDecoded; + wxUint8 *pDecode = sc->m_pktDataPtr; + int nDecodeSiz = sc->m_pktRemainingSiz; + + //check to see if the sc has already been decoded in our sample range + + sc->m_frameValid = 0; + + if (flushing) + { + // If we're flushing the decoders we don't actually have any new data to decode. + pDecode = NULL; + nDecodeSiz = 0; + } + else + { + if (!sc->m_pktValid || (sc->m_pktRemainingSiz <= 0)) + { + //No more data + return -1; + } + } + + // Reallocate the audio sample buffer if it's smaller than the frame size. + if (!flushing) + { + // av_fast_realloc() will only reallocate the buffer if m_decodedAudioSamplesSiz is + // smaller than third parameter. It also returns new size in m_decodedAudioSamplesSiz + //\warning { for some reason using the following macro call right in the function call + // causes Audacity to crash in some unknown place. With "newsize" it works fine } + int newsize = FFMAX(sc->m_pkt.size*sizeof(*sc->m_decodedAudioSamples), AVCODEC_MAX_AUDIO_FRAME_SIZE); + sc->m_decodedAudioSamples = (int16_t*)FFmpegLibsInst->av_fast_realloc(sc->m_decodedAudioSamples, + &sc->m_decodedAudioSamplesSiz, + newsize + ); + + if (sc->m_decodedAudioSamples == NULL) + { + //Can't allocate bytes + return -1; + } + } + + // avcodec_decode_audio2() expects the size of the output buffer as the 3rd parameter but + // also returns the number of bytes it decoded in the same parameter. + sc->m_decodedAudioSamplesValidSiz = sc->m_decodedAudioSamplesSiz; + nBytesDecoded = FFmpegLibsInst->avcodec_decode_audio2(sc->m_codecCtx, + sc->m_decodedAudioSamples, // out + &sc->m_decodedAudioSamplesValidSiz, // in/out + pDecode, nDecodeSiz); // in + + if (nBytesDecoded < 0) + { + // Decoding failed. Don't stop. + return -1; + } + + // We may not have read all of the data from this packet. If so, the user can call again. + // Whether or not they do depends on if m_pktRemainingSiz == 0 (they can check). + sc->m_pktDataPtr += nBytesDecoded; + sc->m_pktRemainingSiz -= nBytesDecoded; + + // At this point it's normally safe to assume that we've read some samples. However, the MPEG + // audio decoder is broken. If this is the case then we just return with m_frameValid == 0 + // but m_pktRemainingSiz perhaps != 0, so the user can call again. + if (sc->m_decodedAudioSamplesValidSiz > 0) + { + sc->m_frameValid = 1; + + //stick it in the cache. + //TODO- consider growing/unioning a few cache buffers like WaveCache does. + //however we can't use wavecache as it isn't going to handle our stereo interleaved part, and isn't for samples + //However if other ODDecode tasks need this, we should do a new class for caching. + FFMpegDecodeCache* cache = new FFMpegDecodeCache; + //len is number of samples per channel + cache->numChannels = sc->m_stream->codec->channels; + + cache->len = (sc->m_decodedAudioSamplesValidSiz/sizeof(int16_t) )/cache->numChannels; + cache->start=mCurrentPos; + cache->samplePtr = (int16_t*) malloc(sc->m_decodedAudioSamplesValidSiz); + memcpy(cache->samplePtr,sc->m_decodedAudioSamples,sc->m_decodedAudioSamplesValidSiz); + + //TODO:this WILL NOT work with seeking.. + mCurrentPos+=cache->len; + mDecodeCache.push_back(cache); + + mNumSamplesInCache+=cache->len; + + //if the cache is too big, drop some + while(mNumSamplesInCache>kMaxSamplesInCache) + { + mNumSamplesInCache-=mDecodeCache[0]->len; + free(mDecodeCache[0]->samplePtr); + delete mDecodeCache[0]; + mDecodeCache.erase(mDecodeCache.begin()); + } + } + return 0; +} + +int ODFFmpegDecoder::WriteData(streamContext *sc,samplePtr & data, sampleCount &len, unsigned int channel) +{ + // Find the stream index in mScs array + int streamid = -1; + for (int i = 0; i < mNumStreams; i++) + { + if (mScs[i] == sc) + { + streamid = i; + break; + } + } + // Stream is not found. This should not really happen + if (streamid == -1) + { + return 0;//mchinen:changed from 1 so we can return size + } + + int nChannels = sc->m_stream->codec->channels < sc->m_initialchannels ? sc->m_stream->codec->channels : sc->m_initialchannels; + + // Separate the channels + int index = 0; + //get the number of samples per channel + int decodedLen = (sc->m_decodedAudioSamplesValidSiz/sizeof(int16_t))/sc->m_stream->codec->channels; + //cast to 16 bit int. + int16_t* outbuf = (int16_t*)data; + while (index < decodedLen && indexm_decodedAudioSamples[index*nChannels +channel]; + index++; + } + len-=index; + //we update data pointer too- but it is a typedef'd char* so be careful with the pointer math + data+= index* (sizeof(int16_t)/sizeof(*data)); + return index; +} + +#endif + + diff --git a/src/ondemand/ODDecodeFFmpegTask.h b/src/ondemand/ODDecodeFFmpegTask.h new file mode 100644 index 000000000..a39a323fd --- /dev/null +++ b/src/ondemand/ODDecodeFFmpegTask.h @@ -0,0 +1,51 @@ +/* + * ODDecodeFFmpegTask.h + * Audacity + * + * Created by apple on 3/8/10. + * Copyright 2010 __MyCompanyName__. All rights reserved. + * + */ + #include "../Experimental.h" + + #ifdef EXPERIMENTAL_OD_FFMPEG + +#ifndef __ODDECODEFFMPEGTASK__ +#define __ODDECODEFFMPEGTASK__ + +#include +#include "ODDecodeTask.h" +#include "ODTaskThread.h" + +class ODFileDecoder; +class WaveTrack; +/// A class representing a modular task to be used with the On-Demand structures. +class ODDecodeFFmpegTask:public ODDecodeTask +{ + public: + + /// Constructs an ODTask + ODDecodeFFmpegTask(void* scs,int numStreams, WaveTrack*** channels, void* formatContext); + virtual ~ODDecodeFFmpegTask(); + + virtual ODTask* Clone(); + ///Creates an ODFileDecoder that decodes a file of filetype the subclass handles. + virtual ODFileDecoder* CreateFileDecoder(const wxString & fileName); + + ///Lets other classes know that this class handles the ffmpeg type + ///Subclasses should override to return respective type. + virtual unsigned int GetODType(){return eODFFMPEG;} + + /// overridden because we cannot always seek - this depends on the file and our confidence which is + /// computed by this function. + virtual bool SeekingAllowed(); + + protected: + WaveTrack*** mChannels; + int mNumStreams; + void* mScs; + void* mFormatContext; +}; +#endif //__ODDECODEFFMPEGTASK__ + +#endif //EXPERIMENTAL_OD_FFMPEG \ No newline at end of file diff --git a/src/ondemand/ODDecodeTask.cpp b/src/ondemand/ODDecodeTask.cpp index 11c8a1fea..aee34ace0 100644 --- a/src/ondemand/ODDecodeTask.cpp +++ b/src/ondemand/ODDecodeTask.cpp @@ -219,6 +219,17 @@ void ODDecodeTask::OrderBlockFiles(std::vector &unorderedBlo } + +///changes the tasks associated with this Waveform to process the task from a different point in the track +///this is overridden from ODTask because certain classes don't allow users to seek sometimes, or not at all. +void ODDecodeTask::DemandTrackUpdate(WaveTrack* track, double seconds) +{ + //only update if the subclass says we can seek. + if(SeekingAllowed()) + ODTask::DemandTrackUpdate(track,seconds); +} + + ///there could be the ODBlockFiles of several FLACs in one track (after copy and pasting) ///so we keep a list of decoders that keep track of the file names, etc, and check the blocks against them. ///Blocks that have IsDataAvailable()==false are blockfiles to be decoded. if BlockFile::GetDecodeType()==ODDecodeTask::GetODType() then @@ -230,7 +241,11 @@ ODFileDecoder* ODDecodeTask::GetOrCreateMatchingFileDecoder(ODDecodeBlockFile* b //see if the filename matches any of our decoders, if so, return it. for(int i=0;i<(int)mDecoders.size();i++) { - if(mDecoders[i]->GetFileName()==blockFile->GetAudioFileName().GetFullPath()) + //we check filename and decode type, since two types of ODDecoders might work with the same filetype + //e.g., FFmpeg and LibMad import both do MP3s. TODO: is this necessary? in theory we filter this when + //updating our list of blockfiles. + if(mDecoders[i]->GetFileName()==blockFile->GetAudioFileName().GetFullPath() && + GetODType() == blockFile->GetDecodeType() ) { ret = mDecoders[i]; break; diff --git a/src/ondemand/ODDecodeTask.h b/src/ondemand/ODDecodeTask.h index 41c6fa839..c766963d3 100644 --- a/src/ondemand/ODDecodeTask.h +++ b/src/ondemand/ODDecodeTask.h @@ -48,6 +48,14 @@ class ODDecodeTask:public ODTask virtual ODTask* Clone()=0; + /// subclasses need to override this if they cannot always seek. + /// seeking will be enabled once this is true. + virtual bool SeekingAllowed(){return true;} + + ///changes the tasks associated with this Waveform to process the task from a different point in the track + ///this is overridden from ODTask because certain classes don't allow users to seek sometimes, or not at all. + virtual void DemandTrackUpdate(WaveTrack* track, double seconds); + ///Return the task name virtual const char* GetTaskName(){return "ODDecodeTask";}