1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-02 08:59:28 +02:00

Add seek support to OD-based FFmpeg import.

Also some minor refactoring.
This feature works with mp3 right now - may work on other formats, but that will be a seperate commit.
It is not enabled so most of the changes won't even be compiled, and those that do won't be run.
To enable it uncommment the EXPERIMENTAL_ODFFMPEG def in Experimental.h
This commit is contained in:
mchinen 2010-07-05 14:38:47 +00:00
parent 4d244e93ae
commit 5a5957b422
9 changed files with 262 additions and 136 deletions

View File

@ -311,28 +311,29 @@ bool ODDecodeBlockFile::IsDataAvailable()
/// Write the summary to disk, using the derived ReadData() to get the data /// Write the summary to disk, using the derived ReadData() to get the data
/// Here, the decoder ODTask associated with this file must fetch the samples with /// Here, the decoder ODTask associated with this file must fetch the samples with
/// the ODDecodeTask::Decode() method. /// the ODDecodeTask::Decode() method.
void ODDecodeBlockFile::WriteODDecodeBlockFile() int ODDecodeBlockFile::WriteODDecodeBlockFile()
{ {
// To build the summary data, call ReadData (implemented by the // To build the summary data, call ReadData (implemented by the
// derived classes) to get the sample data // derived classes) to get the sample data
samplePtr sampleData;// = NewSamples(mLen, floatSample); samplePtr sampleData;// = NewSamples(mLen, floatSample);
int ret;
//use the decoder here. //use the decoder here.
mDecoderMutex.Lock(); mDecoderMutex.Lock();
if(!mDecoder) if(!mDecoder)
{ {
mDecoderMutex.Unlock(); mDecoderMutex.Unlock();
return; return -1;
} }
//sampleData and mFormat are set by the decoder. //sampleData and mFormat are set by the decoder.
mDecoder->Decode(sampleData, mFormat, mAliasStart, mLen, mAliasChannel); ret = mDecoder->Decode(sampleData, mFormat, mAliasStart, mLen, mAliasChannel);
mDecoderMutex.Unlock(); mDecoderMutex.Unlock();
if(ret < 0)
return ret; //failure
//the summary is also calculated here. //the summary is also calculated here.
mFileNameMutex.Lock(); mFileNameMutex.Lock();
@ -352,6 +353,8 @@ void ODDecodeBlockFile::WriteODDecodeBlockFile()
mDataAvailableMutex.Lock(); mDataAvailableMutex.Lock();
mDataAvailable=true; mDataAvailable=true;
mDataAvailableMutex.Unlock(); mDataAvailableMutex.Unlock();
return ret;
} }
///sets the file name the summary info will be saved in. threadsafe. ///sets the file name the summary info will be saved in. threadsafe.

View File

@ -89,9 +89,9 @@ class ODDecodeBlockFile : public SimpleBlockFile
virtual void Recover(void); virtual void Recover(void);
///A public interface to WriteSummary ///A public interface to WriteSummary
void DoWriteBlockFile(){WriteODDecodeBlockFile();} int DoWriteBlockFile(){return WriteODDecodeBlockFile();}
void WriteODDecodeBlockFile(); int WriteODDecodeBlockFile();
///Sets the value that indicates where the first sample in this block corresponds to the global sequence/clip. Only for display use. ///Sets the value that indicates where the first sample in this block corresponds to the global sequence/clip. Only for display use.
void SetStart(sampleCount startSample){mStart = startSample;} void SetStart(sampleCount startSample){mStart = startSample;}

View File

@ -557,7 +557,7 @@ int FFmpegImportFileHandle::Import(TrackFactory *trackFactory,
//append blockfiles to each stream and add an individual ODDecodeTask for each one. //append blockfiles to each stream and add an individual ODDecodeTask for each one.
for (int s = 0; s < mNumStreams; s++) for (int s = 0; s < mNumStreams; s++)
{ {
ODDecodeFFmpegTask* odTask=new ODDecodeFFmpegTask(mScs,mNumStreams,mChannels,mFormatContext); ODDecodeFFmpegTask* odTask=new ODDecodeFFmpegTask(mScs,mNumStreams,mChannels,mFormatContext, s);
ODFileDecoder* odDecoder = (ODFileDecoder*)odTask->CreateFileDecoder(mFilename); ODFileDecoder* odDecoder = (ODFileDecoder*)odTask->CreateFileDecoder(mFilename);
//each stream has different duration. We need to know it if seeking is to be allowed. //each stream has different duration. We need to know it if seeking is to be allowed.

View File

@ -23,6 +23,11 @@
extern FFmpegLibs *FFmpegLibsInst; extern FFmpegLibs *FFmpegLibsInst;
#include "ODDecodeFFmpegTask.h" #include "ODDecodeFFmpegTask.h"
#define ODFFMPEG_SEEKING_TEST_UNKNOWN 0
#define ODFFMPEG_SEEKING_TEST_FAILED -1
#define ODFFMPEG_SEEKING_TEST_SUCCESS 1
#define kMaxSamplesInCache 4410000 #define kMaxSamplesInCache 4410000
//struct for caching the decoded samples to be used over multiple blockfiles //struct for caching the decoded samples to be used over multiple blockfiles
@ -44,7 +49,7 @@ class ODFFmpegDecoder:public ODFileDecoder
public: public:
///This should handle unicode converted to UTF-8 on mac/linux, but OD TODO:check on windows ///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); ODFFmpegDecoder(const wxString & fileName, streamContext** scs, int numStreams,WaveTrack*** channels, AVFormatContext* formatContext, int streamIndex);
virtual ~ODFFmpegDecoder(); virtual ~ODFFmpegDecoder();
///Decodes the samples for this blockfile from the real file into a float buffer. ///Decodes the samples for this blockfile from the real file into a float buffer.
@ -54,14 +59,19 @@ public:
///this->ReadData(sampleData, floatSample, 0, mLen); ///this->ReadData(sampleData, floatSample, 0, mLen);
///This class should call ReadHeader() first, so it knows the length, and can prepare ///This class should call ReadHeader() first, so it knows the length, and can prepare
///the file object if it needs to. ///the file object if it needs to.
virtual void Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel); virtual int Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel);
///This is a must implement abstract virtual in the superclass. ///This is a must implement abstract virtual in the superclass.
///However it doesn't do anything because ImportFFMpeg does all that for us. ///However it doesn't do anything because ImportFFMpeg does all that for us.
virtual bool ReadHeader() {return true;} virtual bool ReadHeader() {return true;}
bool SeekingAllowed() ;
private: private:
int FillDataFromCache(samplePtr & data, sampleCount start, sampleCount& len, unsigned int channel); void InsertCache(FFMpegDecodeCache* cache);
//puts the actual audio samples into the blockfile's data array
int FillDataFromCache(samplePtr & data, sampleCount & start, sampleCount& len, unsigned int channel);
///REFACTORABLE CODE FROM IMPORT FFMPEG ///REFACTORABLE CODE FROM IMPORT FFMPEG
///! Reads next audio frame ///! 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. ///\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.
@ -73,29 +83,28 @@ private:
///\return 0 on success, -1 if it can't decode any further ///\return 0 on success, -1 if it can't decode any further
int DecodeFrame(streamContext *sc, bool flushing); 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; int mNumStreams;
streamContext **mScs; //!< Array of pointers to stream contexts. Length is mNumStreams. streamContext **mScs; //!< Array of pointers to stream contexts. Length is mNumStreams.
WaveTrack*** mChannels; WaveTrack*** mChannels;
AVFormatContext *mFormatContext; //!< Format description, also contains metadata and some useful info AVFormatContext *mFormatContext; //!< Format description, also contains metadata and some useful info
std::vector<FFMpegDecodeCache*> mDecodeCache; std::vector<FFMpegDecodeCache*> mDecodeCache;
int mNumSamplesInCache; int mNumSamplesInCache;
int mCurrentPos; //the index of the next sample to be decoded sampleCount mCurrentPos; //the index of the next sample to be decoded
sampleCount mCurrentLen; //length of the last packet decoded
bool mSeekingAllowedStatus;
int mStreamIndex;
}; };
//------ ODDecodeFFmpegTask definitions //------ ODDecodeFFmpegTask definitions
ODDecodeFFmpegTask::ODDecodeFFmpegTask(void* scs,int numStreams, WaveTrack*** channels, void* formatContext) ODDecodeFFmpegTask::ODDecodeFFmpegTask(void* scs,int numStreams, WaveTrack*** channels, void* formatContext, int streamIndex)
{ {
mScs=scs; mScs=scs;
mNumStreams=numStreams; mNumStreams=numStreams;
mChannels=channels; mChannels=channels;
mFormatContext = formatContext; mFormatContext = formatContext;
mStreamIndex = streamIndex;
//TODO we probably need to create a new WaveTrack*** pointer and copy. //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 //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); // mScs = (streamContext**)malloc(sizeof(streamContext**)*mFormatContext->nb_streams);
@ -109,7 +118,7 @@ ODTask* ODDecodeFFmpegTask::Clone()
{ {
//we need to create copies of mScs. It would be better to use a reference counter system. //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); ODDecodeFFmpegTask* clone = new ODDecodeFFmpegTask((void*)mScs,mNumStreams,mChannels,mFormatContext,mStreamIndex);
clone->mDemandSample=GetDemandSample(); clone->mDemandSample=GetDemandSample();
//the decoders and blockfiles should not be copied. They are created as the task runs. //the decoders and blockfiles should not be copied. They are created as the task runs.
@ -122,7 +131,7 @@ ODTask* ODDecodeFFmpegTask::Clone()
ODFileDecoder* ODDecodeFFmpegTask::CreateFileDecoder(const wxString & fileName) ODFileDecoder* ODDecodeFFmpegTask::CreateFileDecoder(const wxString & fileName)
{ {
// Open the file for import // Open the file for import
ODFFmpegDecoder *decoder = new ODFFmpegDecoder(fileName, (streamContext**) mScs,mNumStreams,mChannels,(AVFormatContext*)mFormatContext); ODFFmpegDecoder *decoder = new ODFFmpegDecoder(fileName, (streamContext**) mScs,mNumStreams,mChannels,(AVFormatContext*)mFormatContext, mStreamIndex);
mDecoders.push_back(decoder); mDecoders.push_back(decoder);
return decoder; return decoder;
@ -131,22 +140,67 @@ ODFileDecoder* ODDecodeFFmpegTask::CreateFileDecoder(const wxString & fileName)
/// subclasses need to override this if they cannot always seek. /// subclasses need to override this if they cannot always seek.
/// seeking will be enabled once this is true. /// seeking will be enabled once this is true.
bool ODDecodeFFmpegTask::SeekingAllowed() bool ODFFmpegDecoder::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. if(ODFFMPEG_SEEKING_TEST_UNKNOWN != mSeekingAllowedStatus)
return false; return mSeekingAllowedStatus == ODFFMPEG_SEEKING_TEST_SUCCESS;
//we can seek if the following checks pass:
//-sample rate is less than the reciprocal of the time_base of the seeking stream.
//-a seek test has been made and dts updates as expected.
streamContext** scs = (streamContext** )mScs;
//we want to clone this to run a seek test.
AVFormatContext* ic = (AVFormatContext*)mFormatContext;
bool audioStreamExists = false;
AVStream* st;
//test the audio stream(s)
for (unsigned int i = 0; i < ic->nb_streams; i++)
{
if (ic->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO)
{
audioStreamExists = true;
st = ic->streams[i];
if(st->duration <= 0 || st->codec->sample_rate <= 0)
goto test_failed;
//if the time base reciprocal is less than the sample rate it means we can't accurately represent a sample with the timestamp in av.
float time_base_inv = ((float)st->time_base.den/st->time_base.num);
if(time_base_inv < st->codec->sample_rate)
goto test_failed;
}
}
if(!audioStreamExists)
goto test_failed;
//TODO: now try a seek and see if dts/pts (decode/presentation timestamp) is updated as we expected it to be.
//This should be done using a new AVFormatContext clone so that we don't ruin the file pointer if we fail.
// FFmpegLibsInst->url_fseek(mFormatContext->pb,0,SEEK_SET);
if(FFmpegLibsInst->av_seek_frame(mFormatContext,st->index,0,0) >= 0) {
mSeekingAllowedStatus = ODFFMPEG_SEEKING_TEST_SUCCESS;
return SeekingAllowed();
}
test_failed:
mSeekingAllowedStatus = ODFFMPEG_SEEKING_TEST_FAILED;
return SeekingAllowed();
} }
//------ ODDecodeFFmpegFileDecoder //------ ODDecodeFFmpegFileDecoder
ODFFmpegDecoder::ODFFmpegDecoder(const wxString & fileName, streamContext** scs,int numStreams,WaveTrack*** channels, AVFormatContext* formatContext) ODFFmpegDecoder::ODFFmpegDecoder(const wxString & fileName, streamContext** scs,int numStreams,WaveTrack*** channels, AVFormatContext* formatContext, int streamIndex)
:ODFileDecoder(fileName), :ODFileDecoder(fileName),
//mSamplesDone(0), //mSamplesDone(0),
mNumStreams(numStreams), mNumStreams(numStreams),
mScs(scs), mScs(scs),
mFormatContext(formatContext), mFormatContext(formatContext),
mNumSamplesInCache(0), mNumSamplesInCache(0),
mCurrentPos(0) mCurrentPos(0),
mCurrentLen(0),
mSeekingAllowedStatus(ODFFMPEG_SEEKING_TEST_UNKNOWN),
mStreamIndex(streamIndex)
{ {
PickFFmpegLibs(); PickFFmpegLibs();
@ -202,9 +256,13 @@ ODFFmpegDecoder::~ODFFmpegDecoder()
DropFFmpegLibs(); DropFFmpegLibs();
} }
void ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel) //we read the file from left to right, so in some cases it makes more sense not to seek and just carry on the decode if the gap is small enough.
//this value controls this amount. this should be a value that is much larger than the payload for a single packet, and around block file size around 1-10 secs.
#define kDecodeSampleAllowance 400000
//number of jump backwards seeks
#define kMaxSeekRewindAttempts 8
int 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. //it looks like the code in importFFmpeg.cpp only imports 16 bit int - need to see why.
format = int16Sample; format = int16Sample;
@ -214,35 +272,98 @@ void ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCoun
int nChannels; int nChannels;
//TODO update this to work with seek - this only works linearly now. //TODO update this to work with seek - this only works linearly now.
if(mCurrentPos > start) if(mCurrentPos > start && mCurrentPos <= start+len + kDecodeSampleAllowance)
{ {
//this next call takes data, start and len as reference variables and updates them to reflect the new area that is needed. //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); FillDataFromCache(bufStart,start,len,channel);
} }
// mDecodeBuffer=data;
while (len>0 && (sc = ReadNextFrame()) != NULL) //look at the decoding timestamp and see if the next sample that will be decoded is not the next sample we need.
if(len && SeekingAllowed() && (mCurrentPos > start + len || mCurrentPos + kDecodeSampleAllowance < start )) {
sc = mScs[mStreamIndex];
AVStream* st = sc->m_stream;
int stindex = -1;
uint64_t targetts;
printf("attempting seek to %llu\n", start);
//we have to find the index for this stream.
for (unsigned int i = 0; i < mFormatContext->nb_streams; i++) {
if (mFormatContext->streams[i] == sc->m_stream )
stindex =i;
}
if(stindex >=0) {
int numAttempts = 0;
//reset mCurrentPos to a bogus value
mCurrentPos = start+len +1;
while(numAttempts++ < kMaxSeekRewindAttempts && mCurrentPos > start) {
//we want to move slightly before the start of the block file, but not too far ahead
targetts = (start-kDecodeSampleAllowance*numAttempts/kMaxSeekRewindAttempts) * ((double)st->time_base.den/(st->time_base.num * st->codec->sample_rate ));
if(targetts<0)
targetts=0;
if(FFmpegLibsInst->av_seek_frame(mFormatContext,stindex,targetts,0) >= 0){
//find out the dts we've seekd to.
sampleCount actualDecodeStart = 0.5 + st->codec->sample_rate * st->cur_dts * ((double)st->time_base.num/st->time_base.den); //this is mostly safe because den is usually 1 or low number but check for high values.
double actualDecodeStartDouble = 0.5 + st->codec->sample_rate * st->cur_dts * ((double)st->time_base.num/st->time_base.den); //this is mostly safe because den is usually 1 or low number but check for high values.
mCurrentPos = actualDecodeStart;
//if the seek was past our desired position, rewind a bit.
printf("seek ok to %llu samps, float: %f\n",actualDecodeStart,actualDecodeStartDouble);
} else
break;
}
if(mCurrentPos>start){
mSeekingAllowedStatus = ODFFMPEG_SEEKING_TEST_FAILED;
// FFmpegLibsInst->url_fseek(mFormatContext->pb,sc->m_pkt.pos,SEEK_SET);
printf("seek fail, reverting to previous pos\n");
return -1;
}
}
}
bool firstpass = true;
//we decode up to the end of the blockfile
while (len>0 && (mCurrentPos < start+len) && (sc = ReadNextFrame()) != NULL)
{ {
// ReadNextFrame returns 1 if stream is not to be imported // ReadNextFrame returns 1 if stream is not to be imported
if (sc != (streamContext*)1) if (sc != (streamContext*)1)
{ {
nChannels = sc->m_stream->codec->channels < sc->m_initialchannels ? sc->m_stream->codec->channels : sc->m_initialchannels; nChannels = sc->m_stream->codec->channels < sc->m_initialchannels ? sc->m_stream->codec->channels : sc->m_initialchannels;
//find out the dts we've seekd to. can't use the stream->cur_dts because it is faulty. also note that until we do the first seek, pkt.dts can be false and will change for the same samples after the initial seek.
sampleCount actualDecodeStart = 0.52 + sc->m_stream->codec->sample_rate * sc->m_pkt.dts * ((double)sc->m_stream->time_base.num/sc->m_stream->time_base.den); //this is mostly safe because den is usually 1 or low number but check for high values.
double actualDecodeStartdouble = 0.52 + sc->m_stream->codec->sample_rate * sc->m_pkt.dts * ((double)sc->m_stream->time_base.num/sc->m_stream->time_base.den); //this is mostly safe because den is usually 1 or low number but check for high values.
//hack to get rounding to work to neareset frame size since dts isn't exact
actualDecodeStart = ((actualDecodeStart + sc->m_stream->codec->frame_size/2) / sc->m_stream->codec->frame_size) * sc->m_stream->codec->frame_size;
if(actualDecodeStart != mCurrentPos)
printf("ts not matching - now:%llu (%f), last:%llu, lastlen:%llu, start %llu, len %llu\n",actualDecodeStart, actualDecodeStartdouble, mCurrentPos, mCurrentLen, start, len);
//if we've skipped over some samples, fill the gap with silence. This could happen often in the beginning of the file.
if(actualDecodeStart>start && firstpass) {
int amt = actualDecodeStart - start;
FFMpegDecodeCache* cache = new FFMpegDecodeCache;
printf("skipping/zeroing %i samples. - now:%llu (%f), last:%llu, lastlen:%llu, start %llu, len %llu\n",amt,actualDecodeStart, actualDecodeStartdouble, mCurrentPos, mCurrentLen, start, len);
//put it in the cache so the other channels can use it.
cache->numChannels = sc->m_stream->codec->channels;
cache->len = amt;
cache->start=start;
cache->samplePtr = (int16_t*) malloc(amt * cache->numChannels * sizeof(int16_t));
memset(cache->samplePtr, 0, amt * cache->numChannels * sizeof(int16_t));
InsertCache(cache);
}
firstpass=false;
mCurrentPos = actualDecodeStart;
//decode the entire packet (unused bits get saved in cache, so as long as cache size limit is bigger than the //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. //largest packet size, we're ok.
while (sc->m_pktRemainingSiz > 0)// && (res == eProgressSuccess || res == eProgressStopped)) while (sc->m_pktRemainingSiz > 0)
{ //Fill the cache with decoded samples
//if(mCurrentPos < start+origLen)
if (DecodeFrame(sc,false) < 0) if (DecodeFrame(sc,false) < 0)
break; 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 // Cleanup after frame decoding
if (sc->m_pktValid) if (sc->m_pktValid)
{ {
@ -256,7 +377,6 @@ void ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCoun
} }
} }
// Flush the decoders if we're done. // Flush the decoders if we're done.
if(!sc && len>0) if(!sc && len>0)
{ {
@ -264,8 +384,6 @@ void ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCoun
{ {
if (DecodeFrame(mScs[i], true) == 0) if (DecodeFrame(mScs[i], true) == 0)
{ {
WriteData(mScs[i],bufStart,len,channel);
if (mScs[i]->m_pktValid) if (mScs[i]->m_pktValid)
{ {
#if FFMPEG_STABLE #if FFMPEG_STABLE
@ -279,20 +397,27 @@ void ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCoun
} }
} }
//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);
//if for some reason we couldn't get the samples, fill them with silence //if for some reason we couldn't get the samples, fill them with silence
int16_t* outBuf = (int16_t*) bufStart; int16_t* outBuf = (int16_t*) bufStart;
for(int i=0;i<len;i++) for(int i=0;i<len;i++)
outBuf[i]=0; outBuf[i]=0;
return 1;
} }
//puts the actual audio samples into the blockfile's data array
// the minimum amount of cache entries necessary to warrant a binary search. // the minimum amount of cache entries necessary to warrant a binary search.
#define kODFFmpegSearchThreshold 10 #define kODFFmpegSearchThreshold 10
///returns the number of samples filled in from start. ///returns the number of samples filled in from start.
//also updates data and len to reflect new unfilled area - start is unmodified. //also updates data and len to reflect new unfilled area - start is unmodified.
int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount start, sampleCount& len, unsigned int channel) int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount &start, sampleCount& len, unsigned int channel)
{ {
if(mDecodeCache.size() <= 0)
return 0;
int samplesFilled=0; int samplesFilled=0;
//do a search for the best position to start at. //do a search for the best position to start at.
@ -301,7 +426,7 @@ int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount start, samp
//all we need for this to work is a location in the cache array //all we need for this to work is a location in the cache array
//that has a start time of less than our start sample, but try to get closer with binary search //that has a start time of less than our start sample, but try to get closer with binary search
int searchStart = 0; int searchStart = 0;
int searchEnd = mDecodeCache.size()-1; int searchEnd = mDecodeCache.size();
int guess; int guess;
if(searchEnd>kODFFmpegSearchThreshold) if(searchEnd>kODFFmpegSearchThreshold)
{ {
@ -309,20 +434,17 @@ int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount start, samp
//by guessing where our hit will be. //by guessing where our hit will be.
while(searchStart+1<searchEnd) while(searchStart+1<searchEnd)
{ {
guess = searchStart+ (searchEnd-searchStart)* ((float)start - mDecodeCache[searchStart]->start )/mDecodeCache[searchEnd]->start; guess = (searchStart+searchEnd)/2;//find a midpoint. //searchStart+ (searchEnd-searchStart)* ((float)start - mDecodeCache[searchStart]->start )/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) //we want guess to point at the first index that hits even if there are duplicate start times (which can happen)
searchEnd = guess; if(mDecodeCache[guess]->start+mDecodeCache[guess]->len >= start)
searchEnd = --guess;
else else
searchStart = guess; searchStart = guess;
} }
} }
//most recent caches are at the end of the vector, so start there. //this is a sorted array
for(int i=searchStart; i < (int)mDecodeCache.size(); i++) for(int i=searchStart; i < (int)mDecodeCache.size(); i++)
{ {
//check for a cache hit - be careful to include the first/last sample an nothing more. //check for a cache hit - be careful to include the first/last sample an nothing more.
@ -334,13 +456,11 @@ int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount start, samp
//ffmpeg only uses 16 bit ints //ffmpeg only uses 16 bit ints
int16_t* outBuf; int16_t* outBuf;
outBuf = (int16_t*)data; outBuf = (int16_t*)data;
//for debug
//reject buffers that would split us into two pieces because we don't have //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. //a method of dealing with this yet, and it won't happen very often.
if(start<mDecodeCache[i]->start && start+len > mDecodeCache[i]->start+mDecodeCache[i]->len) if(start<mDecodeCache[i]->start && start+len > mDecodeCache[i]->start+mDecodeCache[i]->len)
continue; continue;
int samplesHit; int samplesHit;
int hitStartInCache; int hitStartInCache;
int hitStartInRequest; int hitStartInRequest;
@ -370,8 +490,8 @@ int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount start, samp
len -=samplesHit; len -=samplesHit;
} }
} }
//if we've had our fill, leave //if we've had our fill, leave. if we've passed the point which can have hits, leave.
if(len<=0) if(len<=0 || mDecodeCache[i]->start > start+len)
break; break;
} }
return samplesFilled; return samplesFilled;
@ -504,62 +624,51 @@ int ODFFmpegDecoder::DecodeFrame(streamContext *sc, bool flushing)
cache->samplePtr = (int16_t*) malloc(sc->m_decodedAudioSamplesValidSiz); cache->samplePtr = (int16_t*) malloc(sc->m_decodedAudioSamplesValidSiz);
memcpy(cache->samplePtr,sc->m_decodedAudioSamples,sc->m_decodedAudioSamplesValidSiz); memcpy(cache->samplePtr,sc->m_decodedAudioSamples,sc->m_decodedAudioSamplesValidSiz);
//TODO:this WILL NOT work with seeking.. InsertCache(cache);
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; return 0;
} }
int ODFFmpegDecoder::WriteData(streamContext *sc,samplePtr & data, sampleCount &len, unsigned int channel) void ODFFmpegDecoder::InsertCache(FFMpegDecodeCache* cache) {
{ int searchStart = 0;
// Find the stream index in mScs array int searchEnd = mDecodeCache.size(); //size() is also a valid insert index.
int streamid = -1; int guess = 0;
for (int i = 0; i < mNumStreams; i++) //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.
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; // printf("inserting cache start %llu, mCurrentPos %llu\n", cache->start, mCurrentPos);
while(searchStart<searchEnd)
// 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 && index<len)
{ {
//this is interleaved guess = (searchStart+searchEnd)/2;//searchStart+ (searchEnd-searchStart)* ((float)cache->start - mDecodeCache[searchStart]->start )/mDecodeCache[searchEnd]->start;
outbuf[index]= sc->m_decodedAudioSamples[index*nChannels +channel]; //check greater than OR equals because we want to insert infront of old dupes.
index++; if(mDecodeCache[guess]->start>= cache->start) {
// if(mDecodeCache[guess]->start == cache->start) {
// printf("dupe! start cache %llu start new cache %llu, mCurrentPos %llu\n",mDecodeCache[guess]->start, cache->start, mCurrentPos);
// }
searchEnd = guess;
}
else
searchStart = ++guess;
}
mCurrentLen = cache->len;
mCurrentPos=cache->start+cache->len;
mDecodeCache.insert(mDecodeCache.begin()+guess, cache);
// mDecodeCache.push_back(cache);
mNumSamplesInCache+=cache->len;
//if the cache is too big, drop some.
while(mNumSamplesInCache>kMaxSamplesInCache)
{
int dropindex;
//drop which ever index is further from our newly added one.
dropindex = (guess > mDecodeCache.size()/2) ? 0 : (mDecodeCache.size()-1);
mNumSamplesInCache-=mDecodeCache[dropindex]->len;
free(mDecodeCache[dropindex]->samplePtr);
delete mDecodeCache[dropindex];
mDecodeCache.erase(mDecodeCache.begin()+dropindex);
} }
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 #endif

View File

@ -25,7 +25,7 @@ class ODDecodeFFmpegTask:public ODDecodeTask
public: public:
/// Constructs an ODTask /// Constructs an ODTask
ODDecodeFFmpegTask(void* scs,int numStreams, WaveTrack*** channels, void* formatContext); ODDecodeFFmpegTask(void* scs,int numStreams, WaveTrack*** channels, void* formatContext, int streamIndex);
virtual ~ODDecodeFFmpegTask(); virtual ~ODDecodeFFmpegTask();
virtual ODTask* Clone(); virtual ODTask* Clone();
@ -36,15 +36,12 @@ class ODDecodeFFmpegTask:public ODDecodeTask
///Subclasses should override to return respective type. ///Subclasses should override to return respective type.
virtual unsigned int GetODType(){return eODFFMPEG;} 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: protected:
WaveTrack*** mChannels; WaveTrack*** mChannels;
int mNumStreams; int mNumStreams;
void* mScs; void* mScs;
void* mFormatContext; void* mFormatContext;
int mStreamIndex;
}; };
#endif //__ODDECODEFFMPEGTASK__ #endif //__ODDECODEFFMPEGTASK__

View File

@ -166,7 +166,7 @@ FLAC__StreamDecoderWriteStatus ODFLACFile::write_callback(const FLAC__Frame *fra
///this->ReadData(sampleData, floatSample, 0, mLen); ///this->ReadData(sampleData, floatSample, 0, mLen);
///This class should call ReadHeader() first, so it knows the length, and can prepare ///This class should call ReadHeader() first, so it knows the length, and can prepare
///the file object if it needs to. ///the file object if it needs to.
void ODFlacDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel) int ODFlacDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel)
{ {
//we need to lock this so the target stays fixed over the seek/write callback. //we need to lock this so the target stays fixed over the seek/write callback.
@ -191,7 +191,7 @@ void ODFlacDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount
if(!mFile->seek_absolute(start)) if(!mFile->seek_absolute(start))
{ {
mFlacFileLock.Unlock(); mFlacFileLock.Unlock();
return; return -1;
} }
while(mDecodeBufferWritePosition<mDecodeBufferLen) while(mDecodeBufferWritePosition<mDecodeBufferLen)
@ -204,6 +204,7 @@ void ODFlacDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount
} }
//insert into blockfile and //insert into blockfile and
//calculate summary happen in ODDecodeBlockFile::WriteODDecodeBlockFile, where this method is also called. //calculate summary happen in ODDecodeBlockFile::WriteODDecodeBlockFile, where this method is also called.
return 1;
} }
///Read header. Subclasses must override. Probably should save the info somewhere. ///Read header. Subclasses must override. Probably should save the info somewhere.

View File

@ -106,7 +106,7 @@ public:
///this->ReadData(sampleData, floatSample, 0, mLen); ///this->ReadData(sampleData, floatSample, 0, mLen);
///This class should call ReadHeader() first, so it knows the length, and can prepare ///This class should call ReadHeader() first, so it knows the length, and can prepare
///the file object if it needs to. ///the file object if it needs to.
virtual void Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel); virtual int Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel);
///Read header. Subclasses must override. Probably should save the info somewhere. ///Read header. Subclasses must override. Probably should save the info somewhere.

View File

@ -49,6 +49,8 @@ void ODDecodeTask::DoSomeInternal()
{ {
bf = mBlockFiles[0]; bf = mBlockFiles[0];
int ret = 1;
//first check to see if the ref count is at least 2. It should have one //first check to see if the ref count is at least 2. It should have one
//from when we added it to this instance's mBlockFiles array, and one from //from when we added it to this instance's mBlockFiles array, and one from
//the Wavetrack/sequence. If it doesn't it has been deleted and we should forget it. //the Wavetrack/sequence. If it doesn't it has been deleted and we should forget it.
@ -63,14 +65,16 @@ void ODDecodeTask::DoSomeInternal()
if(!decoder->IsInitialized()) if(!decoder->IsInitialized())
decoder->Init(); decoder->Init();
bf->SetODFileDecoder(decoder); bf->SetODFileDecoder(decoder);
bf->DoWriteBlockFile(); ret = bf->DoWriteBlockFile();
bf->UnlockRead(); bf->UnlockRead();
if(ret >= 0) {
success = true; success = true;
blockStartSample = bf->GetStart(); blockStartSample = bf->GetStart();
blockEndSample = blockStartSample + bf->GetLength(); blockEndSample = blockStartSample + bf->GetLength();
mComputedBlockFiles++; mComputedBlockFiles++;
} }
}
else else
{ {
//the waveform in the wavetrack now is shorter, so we need to update mMaxBlockFiles //the waveform in the wavetrack now is shorter, so we need to update mMaxBlockFiles
@ -78,7 +82,8 @@ void ODDecodeTask::DoSomeInternal()
mMaxBlockFiles--; mMaxBlockFiles--;
} }
//Release the refcount we placed on it. //Release the refcount we placed on it if we are successful
if(ret >= 0 ) {
bf->Deref(); bf->Deref();
//take it out of the array - we are done with it. //take it out of the array - we are done with it.
mBlockFiles.erase(mBlockFiles.begin()); mBlockFiles.erase(mBlockFiles.begin());
@ -94,6 +99,7 @@ void ODDecodeTask::DoSomeInternal()
} }
mWaveTrackMutex.Unlock(); mWaveTrackMutex.Unlock();
} }
}
//update percentage complete. //update percentage complete.
CalculatePercentComplete(); CalculatePercentComplete();
@ -106,6 +112,15 @@ void ODDecodeTask::CalculatePercentComplete()
mPercentCompleteMutex.Unlock(); mPercentCompleteMutex.Unlock();
} }
bool ODDecodeTask::SeekingAllowed()
{
for(int i=0;i<mDecoders.size();i++) {
if(!mDecoders[i]->SeekingAllowed())
return false;
}
return true;
}
///by default creates the order of the wavetrack to load. ///by default creates the order of the wavetrack to load.
void ODDecodeTask::Update() void ODDecodeTask::Update()
{ {

View File

@ -48,9 +48,7 @@ class ODDecodeTask:public ODTask
virtual ODTask* Clone()=0; virtual ODTask* Clone()=0;
/// subclasses need to override this if they cannot always seek. virtual bool SeekingAllowed();
/// 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 ///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. ///this is overridden from ODTask because certain classes don't allow users to seek sometimes, or not at all.
@ -113,13 +111,16 @@ public:
virtual bool ReadHeader()=0; virtual bool ReadHeader()=0;
virtual bool Init(){return ReadHeader();} virtual bool Init(){return ReadHeader();}
virtual bool SeekingAllowed(){return true;}
///Decodes the samples for this blockfile from the real file into a float buffer. ///Decodes the samples for this blockfile from the real file into a float buffer.
///This is file specific, so subclasses must implement this only. ///This is file specific, so subclasses must implement this only.
///the buffer should be created by the ODFileDecoder implementing this method. ///the buffer should be created by the ODFileDecoder implementing this method.
///It should set the format parameter so that the client code can deal with it. ///It should set the format parameter so that the client code can deal with it.
///This class should call ReadHeader() first, so it knows the length, and can prepare ///This class should call ReadHeader() first, so it knows the length, and can prepare
///the file object if it needs to. ///the file object if it needs to.
virtual void Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel)=0; ///returns negative value for failure, 0 or positive value for success.
virtual int Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel)=0;
wxString GetFileName(){return mFName;} wxString GetFileName(){return mFName;}