Bug 1363668: P1. Attempt to decode the first frame again if error occurred. r?jwwang
MozReview-Commit-ID: H5gwW7PONc6
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1762,16 +1762,21 @@ MediaFormatReader::NotifyNewOutput(
LOGV("Received new %s sample time:%" PRId64 " duration:%" PRId64,
TrackTypeToStr(aTrack), sample->mTime.ToMicroseconds(),
sample->mDuration.ToMicroseconds());
decoder.mOutput.AppendElement(sample);
decoder.mNumSamplesOutput++;
decoder.mNumOfConsecutiveError = 0;
}
LOG("Done processing new %s samples", TrackTypeToStr(aTrack));
+
+ if (!aResults.IsEmpty()) {
+ // We have decoded our first frame, we can now starts to skip future errors.
+ decoder.mFirstFrameTime.reset();
+ }
ScheduleUpdate(aTrack);
}
void
MediaFormatReader::NotifyError(TrackType aTrack, const MediaResult& aError)
{
MOZ_ASSERT(OnTaskQueue());
NS_WARNING(aError.Description().get());
@@ -2249,17 +2254,17 @@ MediaFormatReader::Update(TrackType aTra
}
if (decoder.HasPromise()) {
needOutput = true;
if (decoder.mOutput.Length()) {
RefPtr<MediaData> output = decoder.mOutput[0];
decoder.mOutput.RemoveElementAt(0);
decoder.mSizeOfQueue -= 1;
- decoder.mLastSampleTime =
+ decoder.mLastDecodedSampleTime =
Some(TimeInterval(output->mTime, output->GetEndTime()));
decoder.mNumSamplesOutputTotal++;
ReturnOutput(output, aTrack);
// We have a decoded sample ready to be returned.
if (aTrack == TrackType::kVideoTrack) {
uint64_t delta =
decoder.mNumSamplesOutputTotal - mLastReportedNumDecodedFrames;
a.mStats.mDecodedFrames = static_cast<uint32_t>(delta);
@@ -2286,25 +2291,25 @@ MediaFormatReader::Update(TrackType aTra
decoder.RejectPromise(decoder.mError.ref(), __func__);
return;
} else if (decoder.HasCompletedDrain()) {
if (decoder.mDemuxEOS) {
LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack));
decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
} else if (decoder.mWaitingForData) {
if (decoder.mDrainState == DrainState::DrainCompleted
- && decoder.mLastSampleTime
+ && decoder.mLastDecodedSampleTime
&& !decoder.mNextStreamSourceID) {
// We have completed draining the decoder following WaitingForData.
// Set up the internal seek machinery to be able to resume from the
// last sample decoded.
LOG("Seeking to last sample time: %" PRId64,
- decoder.mLastSampleTime.ref().mStart.ToMicroseconds());
+ decoder.mLastDecodedSampleTime.ref().mStart.ToMicroseconds());
InternalSeek(aTrack,
- InternalSeekTarget(decoder.mLastSampleTime.ref(), true));
+ InternalSeekTarget(decoder.mLastDecodedSampleTime.ref(), true));
}
if (!decoder.mReceivedNewData) {
LOG("Rejecting %s promise: WAITING_FOR_DATA", TrackTypeToStr(aTrack));
decoder.RejectPromise(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
}
}
decoder.mDrainState = DrainState::None;
@@ -2344,27 +2349,37 @@ MediaFormatReader::Update(TrackType aTra
bool needsNewDecoder =
decoder.mError.ref() == NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER;
if (!needsNewDecoder
&& ++decoder.mNumOfConsecutiveError > decoder.mMaxConsecutiveError) {
NotifyError(aTrack, decoder.mError.ref());
return;
}
decoder.mError.reset();
+
LOG("%s decoded error count %d", TrackTypeToStr(aTrack),
decoder.mNumOfConsecutiveError);
+
+ if (needsNewDecoder) {
+ LOG("Error: Need new decoder");
+ ShutdownDecoder(aTrack);
+ }
+ if (decoder.mFirstFrameTime) {
+ TimeInterval seekInterval = TimeInterval(decoder.mFirstFrameTime.ref(),
+ decoder.mFirstFrameTime.ref());
+ InternalSeek(aTrack, InternalSeekTarget(seekInterval, false));
+ return;
+ }
+
TimeUnit nextKeyframe;
if (aTrack == TrackType::kVideoTrack && !decoder.HasInternalSeekPending()
&& NS_SUCCEEDED(
decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) {
- if (needsNewDecoder) {
- ShutdownDecoder(aTrack);
- }
SkipVideoDemuxToNextKeyFrame(
- decoder.mLastSampleTime.refOr(TimeInterval()).Length());
+ decoder.mLastDecodedSampleTime.refOr(TimeInterval()).Length());
} else if (aTrack == TrackType::kAudioTrack) {
decoder.Flush();
}
return;
}
bool needInput = NeedInput(decoder);
@@ -2499,24 +2514,26 @@ MediaFormatReader::ResetDecode(TrackSet
WaitForDataRejectValue::CANCELED), __func__);
}
// Reset miscellaneous seeking state.
mPendingSeekTime.reset();
if (HasVideo() && aTracks.contains(TrackInfo::kVideoTrack)) {
mVideo.ResetDemuxer();
+ mVideo.mFirstFrameTime = Some(media::TimeUnit::Zero());
Reset(TrackInfo::kVideoTrack);
if (mVideo.HasPromise()) {
mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
}
}
if (HasAudio() && aTracks.contains(TrackInfo::kAudioTrack)) {
mAudio.ResetDemuxer();
+ mVideo.mFirstFrameTime = Some(media::TimeUnit::Zero());
Reset(TrackInfo::kAudioTrack);
if (mAudio.HasPromise()) {
mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
}
}
return MediaDecoderReader::ResetDecode(aTracks);
}
@@ -2791,16 +2808,17 @@ MediaFormatReader::DoVideoSeek()
void
MediaFormatReader::OnVideoSeekCompleted(TimeUnit aTime)
{
MOZ_ASSERT(OnTaskQueue());
LOGV("Video seeked to %" PRId64, aTime.ToMicroseconds());
mVideo.mSeekRequest.Complete();
+ mVideo.mFirstFrameTime = Some(aTime);
mPreviousDecodedKeyframeTime_us = sNoPreviousDecodedKeyframe;
SetVideoDecodeThreshold();
if (HasAudio() && !mOriginalSeekTarget.IsVideoOnly()) {
MOZ_ASSERT(mPendingSeekTime.isSome());
if (mOriginalSeekTarget.IsFast()) {
// We are performing a fast seek. We need to seek audio to where the
@@ -2873,16 +2891,17 @@ MediaFormatReader::DoAudioSeek()
}
void
MediaFormatReader::OnAudioSeekCompleted(TimeUnit aTime)
{
MOZ_ASSERT(OnTaskQueue());
LOGV("Audio seeked to %" PRId64, aTime.ToMicroseconds());
mAudio.mSeekRequest.Complete();
+ mAudio.mFirstFrameTime = Some(aTime);
mPendingSeekTime.reset();
mSeekPromise.Resolve(aTime, __func__);
}
void
MediaFormatReader::OnAudioSeekFailed(const MediaResult& aError)
{
OnSeekFailed(TrackType::kAudioTrack, aError);
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -194,16 +194,17 @@ private:
, mWaitingForData(false)
, mWaitingForKey(false)
, mReceivedNewData(false)
, mFlushing(false)
, mFlushed(true)
, mDrainState(DrainState::None)
, mNumOfConsecutiveError(0)
, mMaxConsecutiveError(aNumOfMaxError)
+ , mFirstFrameTime(Some(media::TimeUnit::Zero()))
, mNumSamplesInput(0)
, mNumSamplesOutput(0)
, mNumSamplesOutputTotal(0)
, mNumSamplesSkippedTotal(0)
, mSizeOfQueue(0)
, mIsHardwareAccelerated(false)
, mLastStreamSourceID(UINT32_MAX)
, mIsNullDecode(false)
@@ -273,16 +274,21 @@ private:
void RequestDrain()
{
MOZ_RELEASE_ASSERT(mDrainState == DrainState::None);
mDrainState = DrainState::DrainRequested;
}
uint32_t mNumOfConsecutiveError;
uint32_t mMaxConsecutiveError;
+ // Set when we haven't yet decoded the first frame.
+ // Cleared once the first frame has been decoded.
+ // This is used to determine, upon error, if we should try again to decode
+ // the frame, or skip to the next keyframe.
+ Maybe<media::TimeUnit> mFirstFrameTime;
Maybe<MediaResult> mError;
bool HasFatalError() const
{
if (!mError.isSome()) {
return false;
}
if (mError.ref() == NS_ERROR_DOM_MEDIA_DECODE_ERR) {
@@ -299,18 +305,18 @@ private:
return true;
}
}
// If set, all decoded samples prior mTimeThreshold will be dropped.
// Used for internal seeking when a change of stream is detected or when
// encountering data discontinuity.
Maybe<InternalSeekTarget> mTimeThreshold;
- // Time of last sample returned.
- Maybe<media::TimeInterval> mLastSampleTime;
+ // Time of last decoded sample returned.
+ Maybe<media::TimeInterval> mLastDecodedSampleTime;
// Decoded samples returned my mDecoder awaiting being returned to
// state machine upon request.
nsTArray<RefPtr<MediaData>> mOutput;
uint64_t mNumSamplesInput;
uint64_t mNumSamplesOutput;
uint64_t mNumSamplesOutputTotal;
uint64_t mNumSamplesSkippedTotal;
@@ -357,17 +363,17 @@ private:
mDemuxEOS = false;
mWaitingForData = false;
mQueuedSamples.Clear();
mDecodeRequest.DisconnectIfExists();
mDrainRequest.DisconnectIfExists();
mDrainState = DrainState::None;
CancelWaitingForKey();
mTimeThreshold.reset();
- mLastSampleTime.reset();
+ mLastDecodedSampleTime.reset();
mOutput.Clear();
mNumSamplesInput = 0;
mNumSamplesOutput = 0;
mSizeOfQueue = 0;
mNextStreamSourceID.reset();
if (!HasFatalError()) {
mError.reset();
}