|
|
|
@ -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)
|
|
|
|
|
{
|
|
|
|
@ -256,7 +377,6 @@ 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
|
|
|
|
@ -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
|
|
|
|
|
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++)
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
// printf("inserting cache start %llu, mCurrentPos %llu\n", cache->start, mCurrentPos);
|
|
|
|
|
while(searchStart<searchEnd)
|
|
|
|
|
{
|
|
|
|
|
//this is interleaved
|
|
|
|
|
outbuf[index]= sc->m_decodedAudioSamples[index*nChannels +channel];
|
|
|
|
|
index++;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|