Bug 1038031 - Make MP4Reader decode asynchronously - r=kinetik,cpearce

This commit is contained in:
Chris Pearce ext:(%2C%20Edwin%20Flores%20%3Cedwin%40mozilla.com%3E%2C%20Matt%20Woodrow%20%3Cmwoodrow%40mozilla.com%3E) 2014-07-20 12:54:00 +12:00
Родитель a6aadad565
Коммит 1d2f1fef3e
6 изменённых файлов: 306 добавлений и 196 удалений

Просмотреть файл

@ -278,7 +278,8 @@ public:
enum NotDecodedReason {
END_OF_STREAM,
DECODE_ERROR,
WAITING_FOR_DATA
WAITING_FOR_DATA,
CANCELED
};
// Receives the result of a RequestAudioData() call.

Просмотреть файл

@ -632,6 +632,11 @@ bool
MediaDecoderStateMachine::NeedToDecodeAudio()
{
AssertCurrentThreadInMonitor();
SAMPLE_LOG("NeedToDecodeAudio() isDec=%d decToTar=%d minPrl=%d seek=%d enufAud=%d",
IsAudioDecoding(), mDecodeToSeekTarget, mMinimizePreroll,
mState == DECODER_STATE_SEEKING,
HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate));
return IsAudioDecoding() &&
((mState == DECODER_STATE_SEEKING && mDecodeToSeekTarget) ||
(!mMinimizePreroll &&
@ -833,6 +838,11 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
return;
}
if (aReason == RequestSampleCallback::CANCELED) {
DispatchDecodeTasksIfNeeded();
return;
}
// This is an EOS. Finish off the queue, and then handle things based on our
// state.
MOZ_ASSERT(aReason == RequestSampleCallback::END_OF_STREAM);
@ -1722,6 +1732,11 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
!needToDecodeVideo &&
!IsPlaying();
SAMPLE_LOG("DispatchDecodeTasksIfNeeded needAudio=%d dispAudio=%d needVideo=%d dispVid=%d needIdle=%d",
needToDecodeAudio, mAudioRequestPending,
needToDecodeVideo, mVideoRequestPending,
needIdle);
if (needToDecodeAudio) {
EnsureAudioDecodeTaskQueued();
}
@ -1729,10 +1744,6 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
EnsureVideoDecodeTaskQueued();
}
SAMPLE_LOG("DispatchDecodeTasksIfNeeded needAudio=%d dispAudio=%d needVideo=%d dispVid=%d needIdle=%d",
needToDecodeAudio, mAudioRequestPending,
needToDecodeVideo, mVideoRequestPending,
needIdle);
if (needIdle) {
RefPtr<nsIRunnable> event = NS_NewRunnableMethod(
@ -2251,9 +2262,10 @@ void MediaDecoderStateMachine::DecodeSeek()
return;
}
mDecodeToSeekTarget = false;
if (!currentTimeChanged) {
DECODER_LOG("Seek !currentTimeChanged...");
mDecodeToSeekTarget = false;
nsresult rv = mDecodeTaskQueue->Dispatch(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted));
if (NS_FAILED(rv)) {
@ -2387,6 +2399,13 @@ MediaDecoderStateMachine::SeekCompleted()
// Try to decode another frame to detect if we're at the end...
DECODER_LOG("Seek completed, mCurrentFrameTime=%lld", mCurrentFrameTime);
mCurrentSeekTarget = SeekTarget();
// Reset quick buffering status. This ensures that if we began the
// seek while quick-buffering, we won't bypass quick buffering mode
// if we need to buffer after the seek.
mQuickBuffering = false;
// Prevent changes in playback position before 'seeked' is fired for we
// expect currentTime equals seek target in 'seeked' callback.
mScheduler->FreezeScheduling();
@ -2395,11 +2414,6 @@ MediaDecoderStateMachine::SeekCompleted()
NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
}
// Reset quick buffering status. This ensures that if we began the
// seek while quick-buffering, we won't bypass quick buffering mode
// if we need to buffer after the seek.
mQuickBuffering = false;
ScheduleStateMachine();
mScheduler->ThawScheduling();
}

Просмотреть файл

@ -33,8 +33,10 @@ PRLogModuleInfo* GetDemuxerLog() {
return log;
}
#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__))
#define VLOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG+1, (__VA_ARGS__))
#else
#define LOG(...)
#define VLOG(...)
#endif
using namespace mp4_demuxer;
@ -134,14 +136,28 @@ MP4Reader::MP4Reader(AbstractMediaDecoder* aDecoder)
MP4Reader::~MP4Reader()
{
MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
MOZ_COUNT_DTOR(MP4Reader);
Shutdown();
}
class DestroyPDMTask : public nsRunnable {
public:
DestroyPDMTask(nsAutoPtr<PlatformDecoderModule>& aPDM)
: mPDM(aPDM)
{}
NS_IMETHOD Run() MOZ_OVERRIDE {
MOZ_ASSERT(NS_IsMainThread());
mPDM = nullptr;
return NS_OK;
}
private:
nsAutoPtr<PlatformDecoderModule> mPDM;
};
void
MP4Reader::Shutdown()
{
MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
if (mAudio.mDecoder) {
Flush(kAudio);
mAudio.mDecoder->Shutdown();
@ -164,8 +180,10 @@ MP4Reader::Shutdown()
mQueuedVideoSample = nullptr;
if (mPlatform) {
mPlatform->Shutdown();
mPlatform = nullptr;
// PDMs are supposed to be destroyed on the main thread...
nsRefPtr<DestroyPDMTask> task(new DestroyPDMTask(mPlatform));
MOZ_ASSERT(!mPlatform);
NS_DispatchToMainThread(task);
}
}
@ -206,12 +224,10 @@ MP4Reader::Init(MediaDecoderReader* aCloneDonor)
InitLayersBackendType();
mAudio.mTaskQueue = new MediaTaskQueue(
SharedThreadPool::Get(NS_LITERAL_CSTRING("MP4 Audio Decode")));
mAudio.mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
NS_ENSURE_TRUE(mAudio.mTaskQueue, NS_ERROR_FAILURE);
mVideo.mTaskQueue = new MediaTaskQueue(
SharedThreadPool::Get(NS_LITERAL_CSTRING("MP4 Video Decode")));
mVideo.mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
NS_ENSURE_TRUE(mVideo.mTaskQueue, NS_ERROR_FAILURE);
static bool sSetupPrefCache = false;
@ -483,6 +499,176 @@ MP4Reader::GetDecoderData(TrackType aTrack)
return (aTrack == kAudio) ? mAudio : mVideo;
}
void
MP4Reader::RequestVideoData(bool aSkipToNextKeyframe,
int64_t aTimeThreshold)
{
MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
VLOG("RequestVideoData skip=%d time=%lld", aSkipToNextKeyframe, aTimeThreshold);
// Record number of frames decoded and parsed. Automatically update the
// stats counters using the AutoNotifyDecoded stack-based class.
uint32_t parsed = 0, decoded = 0;
AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder);
if (aSkipToNextKeyframe) {
if (!SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed) ||
NS_FAILED(mVideo.mDecoder->Flush())) {
NS_WARNING("Failed to skip/flush video when skipping-to-next-keyframe.");
}
}
auto& decoder = GetDecoderData(kVideo);
MonitorAutoLock lock(decoder.mMonitor);
decoder.mOutputRequested = true;
ScheduleUpdate(kVideo);
// Report the number of "decoded" frames as the difference in the
// mNumSamplesOutput field since the last time we were called.
uint64_t delta = mVideo.mNumSamplesOutput - mLastReportedNumDecodedFrames;
decoded = static_cast<uint32_t>(delta);
mLastReportedNumDecodedFrames = mVideo.mNumSamplesOutput;
}
void
MP4Reader::RequestAudioData()
{
MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
VLOG("RequestAudioData");
auto& decoder = GetDecoderData(kAudio);
MonitorAutoLock lock(decoder.mMonitor);
decoder.mOutputRequested = true;
ScheduleUpdate(kAudio);
}
void
MP4Reader::ScheduleUpdate(TrackType aTrack)
{
auto& decoder = GetDecoderData(aTrack);
decoder.mMonitor.AssertCurrentThreadOwns();
if (decoder.mUpdateScheduled) {
return;
}
VLOG("SchedulingUpdate(%s)", TrackTypeToStr(aTrack));
decoder.mUpdateScheduled = true;
RefPtr<nsIRunnable> task(
NS_NewRunnableMethodWithArg<TrackType>(this, &MP4Reader::Update, aTrack));
GetTaskQueue()->Dispatch(task.forget());
}
bool
MP4Reader::NeedInput(DecoderData& aDecoder)
{
aDecoder.mMonitor.AssertCurrentThreadOwns();
// We try to keep a few more compressed samples input than decoded samples
// have been output, provided the state machine has requested we send it a
// decoded sample. To account for H.264 streams which may require a longer
// run of input than we input, decoders fire an "input exhausted" callback,
// which overrides our "few more samples" threshold.
return
!aDecoder.mError &&
!aDecoder.mEOS &&
aDecoder.mOutputRequested &&
aDecoder.mOutput.IsEmpty() &&
(aDecoder.mInputExhausted ||
aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput < aDecoder.mDecodeAhead);
}
void
MP4Reader::Update(TrackType aTrack)
{
MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
bool needInput = false;
bool needOutput = false;
bool eos = false;
auto& decoder = GetDecoderData(aTrack);
nsRefPtr<MediaData> output;
{
MonitorAutoLock lock(decoder.mMonitor);
decoder.mUpdateScheduled = false;
if (NeedInput(decoder)) {
needInput = true;
decoder.mInputExhausted = false;
decoder.mNumSamplesInput++;
}
needOutput = decoder.mOutputRequested;
if (needOutput && !decoder.mOutput.IsEmpty()) {
output = decoder.mOutput[0];
decoder.mOutput.RemoveElementAt(0);
}
eos = decoder.mEOS;
}
VLOG("Update(%s) ni=%d no=%d iex=%d or=%d fl=%d",
TrackTypeToStr(aTrack),
needInput,
needOutput,
decoder.mInputExhausted,
decoder.mOutputRequested,
decoder.mIsFlushing);
if (needInput) {
MP4Sample* sample = PopSample(aTrack);
if (sample) {
decoder.mDecoder->Input(sample);
} else {
{
MonitorAutoLock lock(decoder.mMonitor);
MOZ_ASSERT(!decoder.mEOS);
eos = decoder.mEOS = true;
}
decoder.mDecoder->Drain();
}
}
if (needOutput) {
if (output) {
ReturnOutput(output, aTrack);
} else if (eos) {
ReturnEOS(aTrack);
}
}
}
void
MP4Reader::ReturnOutput(MediaData* aData, TrackType aTrack)
{
auto& decoder = GetDecoderData(aTrack);
{
MonitorAutoLock lock(decoder.mMonitor);
MOZ_ASSERT(decoder.mOutputRequested);
decoder.mOutputRequested = false;
if (decoder.mDiscontinuity) {
decoder.mDiscontinuity = false;
aData->mDiscontinuity = true;
}
}
if (aTrack == kAudio) {
AudioData* audioData = static_cast<AudioData*>(aData);
if (audioData->mChannels != mInfo.mAudio.mChannels ||
audioData->mRate != mInfo.mAudio.mRate) {
LOG("MP4Reader::ReturnOutput change of sampling rate:%d->%d",
mInfo.mAudio.mRate, audioData->mRate);
mInfo.mAudio.mRate = audioData->mRate;
mInfo.mAudio.mChannels = audioData->mChannels;
}
GetCallback()->OnAudioDecoded(audioData);
} else if (aTrack == kVideo) {
GetCallback()->OnVideoDecoded(static_cast<VideoData*>(aData));
}
}
void
MP4Reader::ReturnEOS(TrackType aTrack)
{
GetCallback()->OnNotDecoded(aTrack == kAudio ? MediaData::AUDIO_DATA : MediaData::VIDEO_DATA,
RequestSampleCallback::END_OF_STREAM);
}
MP4Sample*
MP4Reader::PopSample(TrackType aTrack)
{
@ -501,108 +687,12 @@ MP4Reader::PopSample(TrackType aTrack)
}
}
// How async decoding works:
//
// When MP4Reader::Decode() is called:
// * Lock the DecoderData. We assume the state machine wants
// output from the decoder (in future, we'll assume decoder wants input
// when the output MediaQueue isn't "full").
// * Cache the value of mNumSamplesOutput, as prevFramesOutput.
// * While we've not output data (mNumSamplesOutput != prevNumFramesOutput)
// and while we still require input, we demux and input data in the reader.
// We assume we require input if
// ((mNumSamplesInput - mNumSamplesOutput) < sDecodeAheadMargin) or
// mInputExhausted is true. Before we send input, we reset mInputExhausted
// and increment mNumFrameInput, and drop the lock on DecoderData.
// * Once we no longer require input, we wait on the DecoderData
// lock for output, or for the input exhausted callback. If we receive the
// input exhausted callback, we go back and input more data.
// * When our output callback is called, we take the DecoderData lock and
// increment mNumSamplesOutput. We notify the DecoderData lock. This will
// awaken the Decode thread, and unblock it, and it will return.
bool
MP4Reader::Decode(TrackType aTrack)
{
DecoderData& data = GetDecoderData(aTrack);
MOZ_ASSERT(data.mDecoder);
data.mMonitor.Lock();
uint64_t prevNumFramesOutput = data.mNumSamplesOutput;
while (prevNumFramesOutput == data.mNumSamplesOutput) {
data.mMonitor.AssertCurrentThreadOwns();
if (data.mError) {
// Decode error!
data.mMonitor.Unlock();
return false;
}
// Send input to the decoder, if we need to. We assume the decoder
// needs input if it's told us it's out of input, or we're beneath the
// "low water mark" for the amount of input we've sent it vs the amount
// out output we've received. We always try to keep the decoder busy if
// possible, so we try to maintain at least a few input samples ahead,
// if we need output.
while (prevNumFramesOutput == data.mNumSamplesOutput &&
(data.mInputExhausted ||
(data.mNumSamplesInput - data.mNumSamplesOutput) < data.mDecodeAhead) &&
!data.mEOS) {
data.mMonitor.AssertCurrentThreadOwns();
data.mMonitor.Unlock();
nsAutoPtr<MP4Sample> compressed(PopSample(aTrack));
if (!compressed) {
// EOS, or error. Send the decoder a signal to drain.
LOG("MP4Reader: EOS or error - no samples available");
LOG("Draining %s", TrackTypeToStr(aTrack));
data.mMonitor.Lock();
MOZ_ASSERT(!data.mEOS);
data.mEOS = true;
MOZ_ASSERT(!data.mDrainComplete);
data.mDrainComplete = false;
data.mMonitor.Unlock();
data.mDecoder->Drain();
} else {
#ifdef LOG_SAMPLE_DECODE
LOG("PopSample %s time=%lld dur=%lld", TrackTypeToStr(aTrack),
compressed->composition_timestamp, compressed->duration);
#endif
data.mMonitor.Lock();
data.mDrainComplete = false;
data.mInputExhausted = false;
data.mNumSamplesInput++;
data.mMonitor.Unlock();
if (NS_FAILED(data.mDecoder->Input(compressed))) {
return false;
}
// If Input() failed, we let the auto pointer delete |compressed|.
// Otherwise, we assume the decoder will delete it when it's finished
// with it.
compressed.forget();
}
data.mMonitor.Lock();
}
data.mMonitor.AssertCurrentThreadOwns();
while (!data.mError &&
prevNumFramesOutput == data.mNumSamplesOutput &&
(!data.mInputExhausted || data.mEOS) &&
!data.mDrainComplete) {
data.mMonitor.Wait();
}
if (data.mError ||
(data.mEOS && data.mDrainComplete)) {
break;
}
}
data.mMonitor.AssertCurrentThreadOwns();
bool rv = !(data.mDrainComplete || data.mError);
data.mMonitor.Unlock();
return rv;
}
nsresult
MP4Reader::ResetDecode()
{
Flush(kAudio);
MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
Flush(kVideo);
Flush(kAudio);
return MediaDecoderReader::ResetDecode();
}
@ -610,7 +700,7 @@ void
MP4Reader::Output(TrackType aTrack, MediaData* aSample)
{
#ifdef LOG_SAMPLE_DECODE
LOG("Decoded %s sample time=%lld dur=%lld",
VLOG("Decoded %s sample time=%lld dur=%lld",
TrackTypeToStr(aTrack), aSample->mTime, aSample->mDuration);
#endif
@ -620,40 +710,20 @@ MP4Reader::Output(TrackType aTrack, MediaData* aSample)
return;
}
DecoderData& data = GetDecoderData(aTrack);
auto& decoder = GetDecoderData(aTrack);
// Don't accept output while we're flushing.
MonitorAutoLock mon(data.mMonitor);
if (data.mIsFlushing) {
MonitorAutoLock mon(decoder.mMonitor);
if (decoder.mIsFlushing) {
LOG("MP4Reader produced output while flushing, discarding.");
mon.NotifyAll();
return;
}
switch (aTrack) {
case kAudio: {
MOZ_ASSERT(aSample->mType == MediaData::AUDIO_DATA);
AudioData* audioData = static_cast<AudioData*>(aSample);
AudioQueue().Push(audioData);
if (audioData->mChannels != mInfo.mAudio.mChannels ||
audioData->mRate != mInfo.mAudio.mRate) {
LOG("MP4Reader::Output change of sampling rate:%d->%d",
mInfo.mAudio.mRate, audioData->mRate);
mInfo.mAudio.mRate = audioData->mRate;
mInfo.mAudio.mChannels = audioData->mChannels;
}
break;
}
case kVideo: {
MOZ_ASSERT(aSample->mType == MediaData::VIDEO_DATA);
VideoQueue().Push(static_cast<VideoData*>(aSample));
break;
}
default:
break;
decoder.mOutput.AppendElement(aSample);
decoder.mNumSamplesOutput++;
if (NeedInput(decoder) || decoder.mOutputRequested) {
ScheduleUpdate(aTrack);
}
data.mNumSamplesOutput++;
mon.NotifyAll();
}
void
@ -671,28 +741,26 @@ MP4Reader::InputExhausted(TrackType aTrack)
DecoderData& data = GetDecoderData(aTrack);
MonitorAutoLock mon(data.mMonitor);
data.mInputExhausted = true;
mon.NotifyAll();
ScheduleUpdate(aTrack);
}
void
MP4Reader::Error(TrackType aTrack)
{
DecoderData& data = GetDecoderData(aTrack);
MonitorAutoLock mon(data.mMonitor);
data.mError = true;
mon.NotifyAll();
}
bool
MP4Reader::DecodeAudioData()
{
MOZ_ASSERT(HasAudio() && mPlatform && mAudio.mDecoder);
return Decode(kAudio);
{
MonitorAutoLock mon(data.mMonitor);
data.mError = true;
}
GetCallback()->OnNotDecoded(aTrack == kVideo ? MediaData::VIDEO_DATA : MediaData::AUDIO_DATA,
RequestSampleCallback::DECODE_ERROR);
}
void
MP4Reader::Flush(TrackType aTrack)
{
MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
VLOG("Flush(%s) BEGIN", TrackTypeToStr(aTrack));
DecoderData& data = GetDecoderData(aTrack);
if (!data.mDecoder) {
return;
@ -710,12 +778,28 @@ MP4Reader::Flush(TrackType aTrack)
{
MonitorAutoLock mon(data.mMonitor);
data.mIsFlushing = false;
data.mOutput.Clear();
data.mNumSamplesInput = 0;
data.mNumSamplesOutput = 0;
data.mInputExhausted = false;
if (data.mOutputRequested) {
GetCallback()->OnNotDecoded(aTrack == kVideo ? MediaData::VIDEO_DATA : MediaData::AUDIO_DATA,
RequestSampleCallback::CANCELED);
}
data.mOutputRequested = false;
data.mDiscontinuity = true;
}
if (aTrack == kVideo) {
mQueuedVideoSample = nullptr;
}
VLOG("Flush(%s) END", TrackTypeToStr(aTrack));
}
bool
MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed)
{
MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
MOZ_ASSERT(mVideo.mDecoder);
Flush(kVideo);
@ -725,6 +809,12 @@ MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed
nsAutoPtr<MP4Sample> compressed(PopSample(kVideo));
if (!compressed) {
// EOS, or error. Let the state machine know.
GetCallback()->OnNotDecoded(MediaData::VIDEO_DATA,
RequestSampleCallback::END_OF_STREAM);
{
MonitorAutoLock mon(mVideo.mMonitor);
mVideo.mEOS = true;
}
return false;
}
parsed++;
@ -739,47 +829,16 @@ MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed
return true;
}
bool
MP4Reader::DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold)
{
// Record number of frames decoded and parsed. Automatically update the
// stats counters using the AutoNotifyDecoded stack-based class.
uint32_t parsed = 0, decoded = 0;
AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder);
if (aKeyframeSkip) {
bool ok = SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed);
if (!ok) {
NS_WARNING("Failed to skip demux up to next keyframe");
return false;
}
aKeyframeSkip = false;
nsresult rv = mVideo.mDecoder->Flush();
NS_ENSURE_SUCCESS(rv, false);
}
bool rv = Decode(kVideo);
{
// Report the number of "decoded" frames as the difference in the
// mNumSamplesOutput field since the last time we were called.
MonitorAutoLock mon(mVideo.mMonitor);
uint64_t delta = mVideo.mNumSamplesOutput - mLastReportedNumDecodedFrames;
decoded = static_cast<uint32_t>(delta);
mLastReportedNumDecodedFrames = mVideo.mNumSamplesOutput;
}
return rv;
}
void
MP4Reader::Seek(int64_t aTime,
int64_t aStartTime,
int64_t aEndTime,
int64_t aCurrentTime)
{
LOG("MP4Reader::Seek(%lld)", aTime);
MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
if (!mDecoder->GetResource()->IsTransportSeekable() || !mDemuxer->CanSeek()) {
VLOG("Seek() END (Unseekable)");
GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE);
return;
}
@ -793,7 +852,7 @@ MP4Reader::Seek(int64_t aTime,
mDemuxer->SeekAudio(
mQueuedVideoSample ? mQueuedVideoSample->composition_timestamp : aTime);
}
LOG("MP4Reader::Seek(%lld) exit", aTime);
GetCallback()->OnSeekCompleted(NS_OK);
}

Просмотреть файл

@ -28,6 +28,8 @@ class MP4Stream;
class MP4Reader : public MediaDecoderReader
{
typedef mp4_demuxer::TrackType TrackType;
public:
explicit MP4Reader(AbstractMediaDecoder* aDecoder);
@ -35,10 +37,11 @@ public:
virtual nsresult Init(MediaDecoderReader* aCloneDonor) MOZ_OVERRIDE;
virtual bool DecodeAudioData() MOZ_OVERRIDE;
virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
virtual void RequestVideoData(bool aSkipToNextKeyframe,
int64_t aTimeThreshold) MOZ_OVERRIDE;
virtual void RequestAudioData() MOZ_OVERRIDE;
virtual bool HasAudio() MOZ_OVERRIDE;
virtual bool HasVideo() MOZ_OVERRIDE;
@ -75,6 +78,17 @@ public:
private:
void ReturnEOS(TrackType aTrack);
void ReturnOutput(MediaData* aData, TrackType aTrack);
// Sends input to decoder for aTrack, and output to the state machine,
// if necessary.
void Update(TrackType aTrack);
// Enqueues a task to call Update(aTrack) on the decoder task queue.
// Lock for corresponding track must be held.
void ScheduleUpdate(TrackType aTrack);
void ExtractCryptoInitData(nsTArray<uint8_t>& aInitData);
// Initializes mLayersBackendType if possible.
@ -86,10 +100,11 @@ private:
bool SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed);
// DecoderCallback proxies the MediaDataDecoderCallback calls to these
// functions.
void Output(mp4_demuxer::TrackType aType, MediaData* aSample);
void InputExhausted(mp4_demuxer::TrackType aTrack);
void Error(mp4_demuxer::TrackType aTrack);
bool Decode(mp4_demuxer::TrackType aTrack);
void Flush(mp4_demuxer::TrackType aTrack);
void DrainComplete(mp4_demuxer::TrackType aTrack);
void UpdateIndex();
@ -144,7 +159,10 @@ private:
, mError(false)
, mIsFlushing(false)
, mDrainComplete(false)
, mOutputRequested(false)
, mUpdateScheduled(false)
, mEOS(false)
, mDiscontinuity(false)
{
}
@ -155,6 +173,10 @@ private:
nsRefPtr<MediaTaskQueue> mTaskQueue;
// Callback that receives output and error notifications from the decoder.
nsAutoPtr<DecoderCallback> mCallback;
// Decoded samples returned my mDecoder awaiting being returned to
// state machine upon request.
nsTArray<nsRefPtr<MediaData> > mOutput;
// Monitor that protects all non-threadsafe state; the primitives
// that follow.
Monitor mMonitor;
@ -167,7 +189,10 @@ private:
bool mError;
bool mIsFlushing;
bool mDrainComplete;
bool mOutputRequested;
bool mUpdateScheduled;
bool mEOS;
bool mDiscontinuity;
};
DecoderData mAudio;
DecoderData mVideo;
@ -175,6 +200,10 @@ private:
// decoder.
nsAutoPtr<mp4_demuxer::MP4Sample> mQueuedVideoSample;
// Returns true when the decoder for this track needs input.
// aDecoder.mMonitor must be locked.
bool NeedInput(DecoderData& aDecoder);
// The last number of decoded output frames that we've reported to
// MediaDecoder::NotifyDecoded(). We diff the number of output video
// frames every time that DecodeVideoData() is called, and report the

Просмотреть файл

@ -164,8 +164,9 @@ public:
// media data that the decoder accepts as valid input and produces as
// output is determined when the MediaDataDecoder is created.
//
// All functions must be threadsafe, and be able to be called on an
// arbitrary thread.
// All functions are only called on the decode task queue. Don't block
// inside these functions, unless it's explicitly noted that you should
// (like in Flush() and Drain()).
//
// Decoding is done asynchronously. Any async work can be done on the
// MediaTaskQueue passed into the PlatformDecoderModules's Create*Decoder()

Просмотреть файл

@ -36,6 +36,8 @@ public:
decoder->SetResource(resource);
reader->Init(nullptr);
reader->SetTaskQueue(
new MediaTaskQueue(SharedThreadPool::Get(NS_LITERAL_CSTRING("TestMP4Reader"))));
{
// This needs to be done before invoking GetBuffered. This is normally
// done by MediaDecoderStateMachine.
@ -55,6 +57,10 @@ public:
private:
virtual ~TestBinding()
{
reader->GetTaskQueue()->Dispatch(NS_NewRunnableMethod(reader,
&MP4Reader::Shutdown));
reader->GetTaskQueue()->Shutdown();
decoder = nullptr;
resource = nullptr;
reader = nullptr;