diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index fac74eb6a9..19f1d223ce 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -375,6 +375,9 @@ MediaFormatReader::DecoderFactory::DoCreateDecoder(TrackType aTrack) case TrackType::kVideoTrack: { // Decoders use the layers backend to decide if they can use hardware decoding, // so specify LAYERS_NONE if we want to forcibly disable it. + using Option = CreateDecoderParams::Option; + using OptionSet = CreateDecoderParams::OptionSet; + data.mDecoder = mOwner->mPlatform->CreateDecoder({ ownerData.mInfo ? *ownerData.mInfo->GetAsVideoInfo() @@ -385,7 +388,10 @@ MediaFormatReader::DecoderFactory::DoCreateDecoder(TrackType aTrack) mOwner->GetImageContainer(), mOwner->mCrashHelper, ownerData.mIsBlankDecode, - &result + &result, + OptionSet(ownerData.mHardwareDecodingDisabled + ? Option::HardwareDecoderNotAllowed + : Option::Default) }); break; } @@ -980,6 +986,8 @@ MediaFormatReader::NotifyNewOutput(TrackType aTrack, MediaData* aSample) decoder.mOutput.AppendElement(aSample); decoder.mNumSamplesOutput++; decoder.mNumOfConsecutiveError = 0; + // We have decoded our first frame, we can now start to skip future errors. + decoder.mFirstFrameTime.reset(); ScheduleUpdate(aTrack); } @@ -1474,7 +1482,7 @@ MediaFormatReader::Update(TrackType aTrack) RefPtr output = decoder.mOutput[0]; decoder.mOutput.RemoveElementAt(0); decoder.mSizeOfQueue -= 1; - decoder.mLastSampleTime = + decoder.mLastDecodedSampleTime = Some(TimeInterval(TimeUnit::FromMicroseconds(output->mTime), TimeUnit::FromMicroseconds(output->GetEndTime()))); decoder.mNumSamplesOutputTotal++; @@ -1522,14 +1530,14 @@ MediaFormatReader::Update(TrackType aTrack) LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack)); decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } else if (decoder.mWaitingForData) { - if (wasDraining && decoder.mLastSampleTime && + if (wasDraining && 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: %lld", - decoder.mLastSampleTime.ref().mStart.ToMicroseconds()); - InternalSeek(aTrack, InternalSeekTarget(decoder.mLastSampleTime.ref(), true)); + decoder.mLastDecodedSampleTime.ref().mStart.ToMicroseconds()); + InternalSeek(aTrack, InternalSeekTarget(decoder.mLastDecodedSampleTime.ref(), true)); } if (!decoder.mReceivedNewData) { LOG("Rejecting %s promise: WAITING_FOR_DATA", TrackTypeToStr(aTrack)); @@ -1565,24 +1573,52 @@ MediaFormatReader::Update(TrackType aTrack) if (decoder.mError && !decoder.HasFatalError()) { decoder.mDecodePending = false; - bool needsNewDecoder = decoder.mError.ref() == NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER; + + MOZ_RELEASE_ASSERT(!decoder.HasInternalSeekPending(), + "No error can occur while an internal seek is pending"); + + nsCString error; + bool firstFrameDecodingFailedWithHardware = + decoder.mFirstFrameTime && + decoder.mError.ref() == NS_ERROR_DOM_MEDIA_DECODE_ERR && + decoder.mDecoder && decoder.mDecoder->IsHardwareAccelerated(error) && + !decoder.mHardwareDecodingDisabled; + bool needsNewDecoder = + decoder.mError.ref() == NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER || + firstFrameDecodingFailedWithHardware; if (!needsNewDecoder && ++decoder.mNumOfConsecutiveError > decoder.mMaxConsecutiveError) { NotifyError(aTrack, decoder.mError.ref()); return; } + if (firstFrameDecodingFailedWithHardware) { + decoder.mHardwareDecodingDisabled = true; + } decoder.mError.reset(); LOG("%s decoded error count %d", TrackTypeToStr(aTrack), decoder.mNumOfConsecutiveError); + + if (needsNewDecoder) { + LOG("Error: Need new decoder"); + decoder.ShutdownDecoder(); + } + if (decoder.mFirstFrameTime) { + TimeInterval seekInterval = TimeInterval(decoder.mFirstFrameTime.ref(), + decoder.mFirstFrameTime.ref()); + InternalSeek(aTrack, InternalSeekTarget(seekInterval, false)); + return; + } + media::TimeUnit nextKeyframe; if (aTrack == TrackType::kVideoTrack && !decoder.HasInternalSeekPending() && - NS_SUCCEEDED(decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) { - if (needsNewDecoder) { - decoder.ShutdownDecoder(); - } - SkipVideoDemuxToNextKeyFrame(decoder.mLastSampleTime.refOr(TimeInterval()).Length()); + NS_SUCCEEDED(decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe)) && + !nextKeyframe.IsInfinite()) { + SkipVideoDemuxToNextKeyFrame(decoder.mLastDecodedSampleTime.refOr(TimeInterval()).Length()); return; } else if (aTrack == TrackType::kAudioTrack) { decoder.Flush(); + } else { + // We can't recover from this error. + NotifyError(aTrack, NS_ERROR_DOM_MEDIA_FATAL_ERR); } } @@ -1723,6 +1759,7 @@ MediaFormatReader::ResetDecode(TrackSet aTracks) if (HasVideo() && aTracks.contains(TrackInfo::kVideoTrack)) { mVideo.ResetDemuxer(); + mVideo.mFirstFrameTime = Some(media::TimeUnit()); Reset(TrackInfo::kVideoTrack); if (mVideo.HasPromise()) { mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__); @@ -1731,6 +1768,7 @@ MediaFormatReader::ResetDecode(TrackSet aTracks) if (HasAudio() && aTracks.contains(TrackInfo::kAudioTrack)) { mAudio.ResetDemuxer(); + mVideo.mFirstFrameTime = Some(media::TimeUnit()); Reset(TrackInfo::kAudioTrack); if (mAudio.HasPromise()) { mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__); @@ -2089,6 +2127,7 @@ MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime) LOGV("Video seeked to %lld", aTime.ToMicroseconds()); mVideo.mSeekRequest.Complete(); + mVideo.mFirstFrameTime = Some(aTime); mPreviousDecodedKeyframeTime_us = sNoPreviousDecodedKeyframe; SetVideoDecodeThreshold(); @@ -2170,6 +2209,7 @@ MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime) MOZ_ASSERT(OnTaskQueue()); LOGV("Audio seeked to %lld", aTime.ToMicroseconds()); mAudio.mSeekRequest.Complete(); + mAudio.mFirstFrameTime = Some(aTime); mPendingSeekTime.reset(); mSeekPromise.Resolve(aTime, __func__); } diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h index c0b3abdfd2..c6d493f10d 100644 --- a/dom/media/MediaFormatReader.h +++ b/dom/media/MediaFormatReader.h @@ -241,6 +241,7 @@ private: , mDrainComplete(false) , mNumOfConsecutiveError(0) , mMaxConsecutiveError(aNumOfMaxError) + , mFirstFrameTime(Some(media::TimeUnit())) , mNumSamplesInput(0) , mNumSamplesOutput(0) , mNumSamplesOutputTotal(0) @@ -249,6 +250,7 @@ private: , mIsHardwareAccelerated(false) , mLastStreamSourceID(UINT32_MAX) , mIsBlankDecode(false) + , mHardwareDecodingDisabled(false) {} MediaFormatReader* mOwner; @@ -322,6 +324,11 @@ private: 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 mFirstFrameTime; Maybe mError; bool HasFatalError() const @@ -347,8 +354,8 @@ private: // Used for internal seeking when a change of stream is detected or when // encountering data discontinuity. Maybe mTimeThreshold; - // Time of last sample returned. - Maybe mLastSampleTime; + // Time of last decoded sample returned. + Maybe mLastDecodedSampleTime; // Decoded samples returned my mDecoder awaiting being returned to // state machine upon request. @@ -411,7 +418,7 @@ private: mDraining = false; mDrainComplete = false; mTimeThreshold.reset(); - mLastSampleTime.reset(); + mLastDecodedSampleTime.reset(); mOutput.Clear(); mNumSamplesInput = 0; mNumSamplesOutput = 0; @@ -443,6 +450,7 @@ private: Maybe mFirstDemuxedSampleTime; // Use BlankDecoderModule or not. bool mIsBlankDecode; + bool mHardwareDecodingDisabled; }; diff --git a/dom/media/platforms/PlatformDecoderModule.h b/dom/media/platforms/PlatformDecoderModule.h index ae408272b9..9192fe48a0 100644 --- a/dom/media/platforms/PlatformDecoderModule.h +++ b/dom/media/platforms/PlatformDecoderModule.h @@ -11,6 +11,7 @@ #include "ImageContainer.h" #include "MediaDecoderReader.h" #include "MediaInfo.h" +#include "mozilla/EnumSet.h" #include "mozilla/MozPromise.h" #include "mozilla/layers/LayersTypes.h" #include "mozilla/layers/KnowsCompositor.h" @@ -47,6 +48,13 @@ struct MOZ_STACK_CLASS CreateDecoderParams final { : mConfig(aConfig) {} + enum class Option + { + Default, + HardwareDecoderNotAllowed, + }; + using OptionSet = EnumSet