mirror of
https://github.com/cookiengineer/audacity
synced 2025-08-01 00:19:27 +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:
parent
4d244e93ae
commit
5a5957b422
@ -311,28 +311,29 @@ bool ODDecodeBlockFile::IsDataAvailable()
|
||||
/// 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
|
||||
/// the ODDecodeTask::Decode() method.
|
||||
void ODDecodeBlockFile::WriteODDecodeBlockFile()
|
||||
int ODDecodeBlockFile::WriteODDecodeBlockFile()
|
||||
{
|
||||
|
||||
// To build the summary data, call ReadData (implemented by the
|
||||
// derived classes) to get the sample data
|
||||
samplePtr sampleData;// = NewSamples(mLen, floatSample);
|
||||
|
||||
int ret;
|
||||
//use the decoder here.
|
||||
mDecoderMutex.Lock();
|
||||
|
||||
if(!mDecoder)
|
||||
{
|
||||
mDecoderMutex.Unlock();
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
//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();
|
||||
|
||||
if(ret < 0)
|
||||
return ret; //failure
|
||||
|
||||
//the summary is also calculated here.
|
||||
mFileNameMutex.Lock();
|
||||
@ -352,6 +353,8 @@ void ODDecodeBlockFile::WriteODDecodeBlockFile()
|
||||
mDataAvailableMutex.Lock();
|
||||
mDataAvailable=true;
|
||||
mDataAvailableMutex.Unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
///sets the file name the summary info will be saved in. threadsafe.
|
||||
|
@ -89,9 +89,9 @@ class ODDecodeBlockFile : public SimpleBlockFile
|
||||
virtual void Recover(void);
|
||||
|
||||
///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.
|
||||
void SetStart(sampleCount startSample){mStart = startSample;}
|
||||
|
@ -557,7 +557,7 @@ int FFmpegImportFileHandle::Import(TrackFactory *trackFactory,
|
||||
//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);
|
||||
ODDecodeFFmpegTask* odTask=new ODDecodeFFmpegTask(mScs,mNumStreams,mChannels,mFormatContext, s);
|
||||
ODFileDecoder* odDecoder = (ODFileDecoder*)odTask->CreateFileDecoder(mFilename);
|
||||
|
||||
//each stream has different duration. We need to know it if seeking is to be allowed.
|
||||
|
@ -23,6 +23,11 @@
|
||||
extern FFmpegLibs *FFmpegLibsInst;
|
||||
#include "ODDecodeFFmpegTask.h"
|
||||
|
||||
|
||||
#define ODFFMPEG_SEEKING_TEST_UNKNOWN 0
|
||||
#define ODFFMPEG_SEEKING_TEST_FAILED -1
|
||||
#define ODFFMPEG_SEEKING_TEST_SUCCESS 1
|
||||
|
||||
#define kMaxSamplesInCache 4410000
|
||||
|
||||
//struct for caching the decoded samples to be used over multiple blockfiles
|
||||
@ -44,7 +49,7 @@ 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);
|
||||
ODFFmpegDecoder(const wxString & fileName, streamContext** scs, int numStreams,WaveTrack*** channels, AVFormatContext* formatContext, int streamIndex);
|
||||
virtual ~ODFFmpegDecoder();
|
||||
|
||||
///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 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);
|
||||
virtual int 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() {return true;}
|
||||
|
||||
bool SeekingAllowed() ;
|
||||
|
||||
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
|
||||
///! 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.
|
||||
@ -73,29 +83,28 @@ private:
|
||||
///\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<FFMpegDecodeCache*> mDecodeCache;
|
||||
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::ODDecodeFFmpegTask(void* scs,int numStreams, WaveTrack*** channels, void* formatContext)
|
||||
ODDecodeFFmpegTask::ODDecodeFFmpegTask(void* scs,int numStreams, WaveTrack*** channels, void* formatContext, int streamIndex)
|
||||
{
|
||||
mScs=scs;
|
||||
mNumStreams=numStreams;
|
||||
mChannels=channels;
|
||||
mFormatContext = formatContext;
|
||||
mStreamIndex = streamIndex;
|
||||
//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);
|
||||
@ -109,7 +118,7 @@ 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);
|
||||
ODDecodeFFmpegTask* clone = new ODDecodeFFmpegTask((void*)mScs,mNumStreams,mChannels,mFormatContext,mStreamIndex);
|
||||
clone->mDemandSample=GetDemandSample();
|
||||
|
||||
//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)
|
||||
{
|
||||
// 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);
|
||||
return decoder;
|
||||
@ -131,22 +140,67 @@ ODFileDecoder* ODDecodeFFmpegTask::CreateFileDecoder(const wxString & fileName)
|
||||
|
||||
/// subclasses need to override this if they cannot always seek.
|
||||
/// 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.
|
||||
return false;
|
||||
if(ODFFMPEG_SEEKING_TEST_UNKNOWN != mSeekingAllowedStatus)
|
||||
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
|
||||
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),
|
||||
//mSamplesDone(0),
|
||||
mNumStreams(numStreams),
|
||||
mScs(scs),
|
||||
mFormatContext(formatContext),
|
||||
mNumSamplesInCache(0),
|
||||
mCurrentPos(0)
|
||||
mCurrentPos(0),
|
||||
mCurrentLen(0),
|
||||
mSeekingAllowedStatus(ODFFMPEG_SEEKING_TEST_UNKNOWN),
|
||||
mStreamIndex(streamIndex)
|
||||
{
|
||||
PickFFmpegLibs();
|
||||
|
||||
@ -202,9 +256,13 @@ ODFFmpegDecoder::~ODFFmpegDecoder()
|
||||
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.
|
||||
format = int16Sample;
|
||||
|
||||
@ -214,35 +272,98 @@ void ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCoun
|
||||
|
||||
int nChannels;
|
||||
|
||||
|
||||
//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.
|
||||
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
|
||||
if (sc != (streamContext*)1)
|
||||
{
|
||||
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
|
||||
//largest packet size, we're ok.
|
||||
while (sc->m_pktRemainingSiz > 0)// && (res == eProgressSuccess || res == eProgressStopped))
|
||||
{
|
||||
//if(mCurrentPos < start+origLen)
|
||||
while (sc->m_pktRemainingSiz > 0)
|
||||
//Fill the cache with decoded samples
|
||||
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)
|
||||
{
|
||||
@ -255,8 +376,7 @@ void ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCoun
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Flush the decoders if we're done.
|
||||
if(!sc && len>0)
|
||||
{
|
||||
@ -264,8 +384,6 @@ void ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCoun
|
||||
{
|
||||
if (DecodeFrame(mScs[i], true) == 0)
|
||||
{
|
||||
WriteData(mScs[i],bufStart,len,channel);
|
||||
|
||||
if (mScs[i]->m_pktValid)
|
||||
{
|
||||
#if FFMPEG_STABLE
|
||||
@ -278,21 +396,28 @@ 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
|
||||
int16_t* outBuf = (int16_t*) bufStart;
|
||||
for(int i=0;i<len;i++)
|
||||
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.
|
||||
#define kODFFmpegSearchThreshold 10
|
||||
///returns the number of samples filled in from start.
|
||||
//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;
|
||||
|
||||
//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
|
||||
//that has a start time of less than our start sample, but try to get closer with binary search
|
||||
int searchStart = 0;
|
||||
int searchEnd = mDecodeCache.size()-1;
|
||||
int searchEnd = mDecodeCache.size();
|
||||
int guess;
|
||||
if(searchEnd>kODFFmpegSearchThreshold)
|
||||
{
|
||||
@ -309,20 +434,17 @@ int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount start, samp
|
||||
//by guessing where our hit will be.
|
||||
while(searchStart+1<searchEnd)
|
||||
{
|
||||
guess = 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.
|
||||
guess = (searchStart+searchEnd)/2;//find a midpoint. //searchStart+ (searchEnd-searchStart)* ((float)start - mDecodeCache[searchStart]->start )/mDecodeCache[searchEnd]->start;
|
||||
|
||||
if(mDecodeCache[guess]->start>start)
|
||||
searchEnd = guess;
|
||||
//we want guess to point at the first index that hits even if there are duplicate start times (which can happen)
|
||||
if(mDecodeCache[guess]->start+mDecodeCache[guess]->len >= start)
|
||||
searchEnd = --guess;
|
||||
else
|
||||
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++)
|
||||
{
|
||||
//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
|
||||
int16_t* outBuf;
|
||||
outBuf = (int16_t*)data;
|
||||
//for debug
|
||||
//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(start<mDecodeCache[i]->start && start+len > mDecodeCache[i]->start+mDecodeCache[i]->len)
|
||||
continue;
|
||||
|
||||
|
||||
int samplesHit;
|
||||
int hitStartInCache;
|
||||
int hitStartInRequest;
|
||||
@ -370,8 +490,8 @@ int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount start, samp
|
||||
len -=samplesHit;
|
||||
}
|
||||
}
|
||||
//if we've had our fill, leave
|
||||
if(len<=0)
|
||||
//if we've had our fill, leave. if we've passed the point which can have hits, leave.
|
||||
if(len<=0 || mDecodeCache[i]->start > start+len)
|
||||
break;
|
||||
}
|
||||
return samplesFilled;
|
||||
@ -504,62 +624,51 @@ int ODFFmpegDecoder::DecodeFrame(streamContext *sc, bool flushing)
|
||||
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());
|
||||
}
|
||||
InsertCache(cache);
|
||||
}
|
||||
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++)
|
||||
void ODFFmpegDecoder::InsertCache(FFMpegDecodeCache* cache) {
|
||||
int searchStart = 0;
|
||||
int searchEnd = mDecodeCache.size(); //size() is also a valid insert index.
|
||||
int guess = 0;
|
||||
//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.
|
||||
|
||||
// printf("inserting cache start %llu, mCurrentPos %llu\n", cache->start, mCurrentPos);
|
||||
while(searchStart<searchEnd)
|
||||
{
|
||||
if (mScs[i] == sc)
|
||||
{
|
||||
streamid = i;
|
||||
break;
|
||||
guess = (searchStart+searchEnd)/2;//searchStart+ (searchEnd-searchStart)* ((float)cache->start - mDecodeCache[searchStart]->start )/mDecodeCache[searchEnd]->start;
|
||||
//check greater than OR equals because we want to insert infront of old dupes.
|
||||
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;
|
||||
}
|
||||
// Stream is not found. This should not really happen
|
||||
if (streamid == -1)
|
||||
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)
|
||||
{
|
||||
return 0;//mchinen:changed from 1 so we can return size
|
||||
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);
|
||||
}
|
||||
|
||||
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 && index<len)
|
||||
{
|
||||
//this is interleaved
|
||||
outbuf[index]= sc->m_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
|
||||
|
||||
|
||||
|
@ -25,7 +25,7 @@ class ODDecodeFFmpegTask:public ODDecodeTask
|
||||
public:
|
||||
|
||||
/// 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 ODTask* Clone();
|
||||
@ -36,15 +36,12 @@ class ODDecodeFFmpegTask:public ODDecodeTask
|
||||
///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;
|
||||
int mStreamIndex;
|
||||
};
|
||||
#endif //__ODDECODEFFMPEGTASK__
|
||||
|
||||
|
@ -166,7 +166,7 @@ FLAC__StreamDecoderWriteStatus ODFLACFile::write_callback(const FLAC__Frame *fra
|
||||
///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.
|
||||
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.
|
||||
@ -191,7 +191,7 @@ void ODFlacDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount
|
||||
if(!mFile->seek_absolute(start))
|
||||
{
|
||||
mFlacFileLock.Unlock();
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
while(mDecodeBufferWritePosition<mDecodeBufferLen)
|
||||
@ -204,6 +204,7 @@ void ODFlacDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount
|
||||
}
|
||||
//insert into blockfile and
|
||||
//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.
|
||||
|
@ -106,7 +106,7 @@ public:
|
||||
///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);
|
||||
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.
|
||||
|
@ -49,6 +49,8 @@ void ODDecodeTask::DoSomeInternal()
|
||||
{
|
||||
bf = mBlockFiles[0];
|
||||
|
||||
int ret = 1;
|
||||
|
||||
//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
|
||||
//the Wavetrack/sequence. If it doesn't it has been deleted and we should forget it.
|
||||
@ -63,13 +65,15 @@ void ODDecodeTask::DoSomeInternal()
|
||||
if(!decoder->IsInitialized())
|
||||
decoder->Init();
|
||||
bf->SetODFileDecoder(decoder);
|
||||
bf->DoWriteBlockFile();
|
||||
ret = bf->DoWriteBlockFile();
|
||||
bf->UnlockRead();
|
||||
|
||||
success = true;
|
||||
blockStartSample = bf->GetStart();
|
||||
blockEndSample = blockStartSample + bf->GetLength();
|
||||
mComputedBlockFiles++;
|
||||
if(ret >= 0) {
|
||||
success = true;
|
||||
blockStartSample = bf->GetStart();
|
||||
blockEndSample = blockStartSample + bf->GetLength();
|
||||
mComputedBlockFiles++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -78,21 +82,23 @@ void ODDecodeTask::DoSomeInternal()
|
||||
mMaxBlockFiles--;
|
||||
}
|
||||
|
||||
//Release the refcount we placed on it.
|
||||
bf->Deref();
|
||||
//take it out of the array - we are done with it.
|
||||
mBlockFiles.erase(mBlockFiles.begin());
|
||||
//Release the refcount we placed on it if we are successful
|
||||
if(ret >= 0 ) {
|
||||
bf->Deref();
|
||||
//take it out of the array - we are done with it.
|
||||
mBlockFiles.erase(mBlockFiles.begin());
|
||||
|
||||
//upddate the gui for all associated blocks. It doesn't matter that we're hitting more wavetracks then we should
|
||||
//because this loop runs a number of times equal to the number of tracks, they probably are getting processed in
|
||||
//the next iteration at the same sample window.
|
||||
mWaveTrackMutex.Lock();
|
||||
for(size_t i=0;i<mWaveTracks.size();i++)
|
||||
{
|
||||
if(success && mWaveTracks[i])
|
||||
mWaveTracks[i]->AddInvalidRegion(blockStartSample,blockEndSample);
|
||||
//upddate the gui for all associated blocks. It doesn't matter that we're hitting more wavetracks then we should
|
||||
//because this loop runs a number of times equal to the number of tracks, they probably are getting processed in
|
||||
//the next iteration at the same sample window.
|
||||
mWaveTrackMutex.Lock();
|
||||
for(size_t i=0;i<mWaveTracks.size();i++)
|
||||
{
|
||||
if(success && mWaveTracks[i])
|
||||
mWaveTracks[i]->AddInvalidRegion(blockStartSample,blockEndSample);
|
||||
}
|
||||
mWaveTrackMutex.Unlock();
|
||||
}
|
||||
mWaveTrackMutex.Unlock();
|
||||
}
|
||||
|
||||
//update percentage complete.
|
||||
@ -106,6 +112,15 @@ void ODDecodeTask::CalculatePercentComplete()
|
||||
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.
|
||||
void ODDecodeTask::Update()
|
||||
{
|
||||
|
@ -48,9 +48,7 @@ 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;}
|
||||
virtual bool SeekingAllowed();
|
||||
|
||||
///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.
|
||||
@ -113,13 +111,16 @@ public:
|
||||
virtual bool ReadHeader()=0;
|
||||
virtual bool Init(){return ReadHeader();}
|
||||
|
||||
virtual bool SeekingAllowed(){return true;}
|
||||
|
||||
///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 should be created by the ODFileDecoder implementing this method.
|
||||
///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
|
||||
///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;}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user