Bug 1068151 - keep decoding with malformat video. r=jya
☠☠ backed out by a381b242fcd5 ☠ ☠
authorAlfredo Yang <ayang@mozilla.com>
Fri, 06 May 2016 01:10:00 +0200
changeset 335375 31dc08c9c5054824e8207ab2cc9af0f91139e766
parent 335374 5896e929871bb58cf454238bcf82bff6c906e834
child 335376 35232e0db8b87969fcff58faac20e6b915a4f827
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya
bugs1068151
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1068151 - keep decoding with malformat video. r=jya
dom/media/Benchmark.cpp
dom/media/Benchmark.h
dom/media/MediaFormatReader.cpp
dom/media/MediaFormatReader.h
dom/media/platforms/PlatformDecoderModule.h
dom/media/platforms/agnostic/OpusDecoder.cpp
dom/media/platforms/agnostic/OpusDecoder.h
dom/media/platforms/agnostic/VPXDecoder.cpp
dom/media/platforms/agnostic/VorbisDecoder.cpp
dom/media/platforms/agnostic/WAVDecoder.cpp
dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
dom/media/platforms/android/AndroidDecoderModule.cpp
dom/media/platforms/apple/AppleATDecoder.cpp
dom/media/platforms/apple/AppleVDADecoder.cpp
dom/media/platforms/apple/AppleVTDecoder.cpp
dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
dom/media/platforms/omx/OmxDataDecoder.cpp
dom/media/platforms/omx/OmxDataDecoder.h
dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
dom/media/platforms/wrappers/FuzzingWrapper.cpp
dom/media/platforms/wrappers/FuzzingWrapper.h
dom/media/platforms/wrappers/H264Converter.cpp
--- a/dom/media/Benchmark.cpp
+++ b/dom/media/Benchmark.cpp
@@ -278,17 +278,17 @@ BenchmarkPlayback::Output(MediaData* aDa
       ref->Dispatch(NS_NewRunnableFunction([ref, decodeFps]() {
         ref->ReturnResult(decodeFps);
       }));
     }
   }));
 }
 
 void
-BenchmarkPlayback::Error()
+BenchmarkPlayback::Error(MediaDataDecoderError aError)
 {
   RefPtr<Benchmark> ref(mMainThreadState);
   Dispatch(NS_NewRunnableFunction([this, ref]() {  MainThreadShutdown(); }));
 }
 
 void
 BenchmarkPlayback::InputExhausted()
 {
--- a/dom/media/Benchmark.h
+++ b/dom/media/Benchmark.h
@@ -27,17 +27,17 @@ class BenchmarkPlayback : public QueueOb
   void DemuxSamples();
   void DemuxNextSample();
   void MainThreadShutdown();
   void InitDecoder(TrackInfo&& aInfo);
 
   // MediaDataDecoderCallback
   // Those methods are called on the MediaDataDecoder's task queue.
   void Output(MediaData* aData) override;
-  void Error() override;
+  void Error(MediaDataDecoderError aError) override;
   void InputExhausted() override;
   void DrainComplete() override;
   bool OnReaderTaskQueue() override;
 
   Atomic<Benchmark*> mMainThreadState;
 
   RefPtr<FlushableTaskQueue> mDecoderTaskQueue;
   RefPtr<MediaDataDecoder> mDecoder;
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -55,18 +55,20 @@ TrackTypeToStr(TrackInfo::TrackType aTra
   }
 }
 
 MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
                                      MediaDataDemuxer* aDemuxer,
                                      VideoFrameContainer* aVideoFrameContainer,
                                      layers::LayersBackend aLayersBackend)
   : MediaDecoderReader(aDecoder)
-  , mAudio(this, MediaData::AUDIO_DATA, Preferences::GetUint("media.audio-decode-ahead", 2))
-  , mVideo(this, MediaData::VIDEO_DATA, Preferences::GetUint("media.video-decode-ahead", 2))
+  , mAudio(this, MediaData::AUDIO_DATA, Preferences::GetUint("media.audio-decode-ahead", 2),
+           Preferences::GetUint("media.audio-max-decode-error", 3))
+  , mVideo(this, MediaData::VIDEO_DATA, Preferences::GetUint("media.video-decode-ahead", 2),
+           Preferences::GetUint("media.video-max-decode-error", 2))
   , mDemuxer(aDemuxer)
   , mDemuxerInitDone(false)
   , mLastReportedNumDecodedFrames(0)
   , mLayersBackendType(aLayersBackend)
   , mInitDone(false)
   , mIsEncrypted(false)
   , mTrackDemuxersMayBlock(false)
   , mDemuxOnly(false)
@@ -499,17 +501,16 @@ RefPtr<MediaDecoderReader::MediaDataProm
 MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe,
                                     int64_t aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(), "No sample requests allowed while seeking");
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests");
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.mSeekRequest.Exists() ||
                         mVideo.mTimeThreshold.isSome());
-  MOZ_DIAGNOSTIC_ASSERT(!mSkipRequest.Exists(), "called mid-skipping");
   MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
   LOGV("RequestVideoData(%d, %lld)", aSkipToNextKeyframe, aTimeThreshold);
 
   if (!HasVideo()) {
     LOG("called with no video track");
     return MediaDataPromise::CreateAndReject(DECODE_ERROR, __func__);
   }
 
@@ -656,16 +657,17 @@ MediaFormatReader::NotifyNewOutput(Track
   if (!decoder.mOutputRequested) {
     LOG("MediaFormatReader produced output while flushing, discarding.");
     return;
   }
   decoder.mOutput.AppendElement(aSample);
   decoder.mNumSamplesOutput++;
   decoder.mNumSamplesOutputTotal++;
   decoder.mNumSamplesOutputTotalSinceTelemetry++;
+  decoder.mNumOfConsecutiveError = 0;
   ScheduleUpdate(aTrack);
 }
 
 void
 MediaFormatReader::NotifyInputExhausted(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("Decoder has requested more %s data", TrackTypeToStr(aTrack));
@@ -684,22 +686,22 @@ MediaFormatReader::NotifyDrainComplete(T
     LOG("MediaFormatReader called DrainComplete() before flushing, ignoring.");
     return;
   }
   decoder.mDrainComplete = true;
   ScheduleUpdate(aTrack);
 }
 
 void
-MediaFormatReader::NotifyError(TrackType aTrack)
+MediaFormatReader::NotifyError(TrackType aTrack, MediaDataDecoderError aError)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("%s Decoding error", TrackTypeToStr(aTrack));
   auto& decoder = GetDecoderData(aTrack);
-  decoder.mError = true;
+  decoder.mError = Some(aError);
   ScheduleUpdate(aTrack);
 }
 
 void
 MediaFormatReader::NotifyWaitingForData(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
@@ -734,17 +736,16 @@ MediaFormatReader::NeedInput(DecoderData
 {
   // 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.mDraining &&
-    !aDecoder.mError &&
     aDecoder.mDecodingRequested &&
     !aDecoder.mDemuxRequest.Exists() &&
     !aDecoder.HasInternalSeekPending() &&
     aDecoder.mOutput.Length() <= aDecoder.mDecodeAhead &&
     (aDecoder.mInputExhausted || !aDecoder.mQueuedSamples.IsEmpty() ||
      aDecoder.mTimeThreshold.isSome() ||
      aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput <= aDecoder.mDecodeAhead);
 }
@@ -812,17 +813,17 @@ MediaFormatReader::UpdateReceivedNewData
   }
 
   decoder.mReceivedNewData = false;
   if (decoder.mTimeThreshold) {
     decoder.mTimeThreshold.ref().mWaiting = false;
   }
   decoder.mWaitingForData = false;
 
-  if (decoder.mError) {
+  if (decoder.HasFatalError()) {
     return false;
   }
 
   if (!mSeekPromise.IsEmpty()) {
     MOZ_ASSERT(!decoder.HasPromise());
     MOZ_DIAGNOSTIC_ASSERT(!mAudio.mTimeThreshold && !mVideo.mTimeThreshold,
                           "InternalSeek must have been aborted when Seek was first called");
     MOZ_DIAGNOSTIC_ASSERT(!mAudio.HasWaitingPromise() && !mVideo.HasWaitingPromise(),
@@ -1131,17 +1132,17 @@ MediaFormatReader::Update(TrackType aTra
           mVideo.mDecoder && mVideo.mDecoder->IsHardwareAccelerated(error);
       }
       RefPtr<MediaData> output = decoder.mOutput[0];
       decoder.mOutput.RemoveElementAt(0);
       decoder.mSizeOfQueue -= 1;
       decoder.mLastSampleTime =
         Some(media::TimeUnit::FromMicroseconds(output->mTime));
       ReturnOutput(output, aTrack);
-    } else if (decoder.mError) {
+    } else if (decoder.HasFatalError()) {
       LOG("Rejecting %s promise: DECODE_ERROR", TrackTypeToStr(aTrack));
       decoder.RejectPromise(DECODE_ERROR, __func__);
       return;
     } else if (decoder.mDrainComplete) {
       bool wasDraining = decoder.mDraining;
       decoder.mDrainComplete = false;
       decoder.mDraining = false;
       if (decoder.mDemuxEOS) {
@@ -1172,16 +1173,31 @@ MediaFormatReader::Update(TrackType aTra
     }
   }
 
   if (decoder.mNeedDraining) {
     DrainDecoder(aTrack);
     return;
   }
 
+  if (decoder.mError.isSome() &&
+      decoder.mError.ref() == MediaDataDecoderError::DECODE_ERROR) {
+    if (++decoder.mNumOfConsecutiveError > decoder.mMaxConsecutiveError) {
+      NotifyError(aTrack);
+      return;
+    }
+    LOG("%s decoded error count %d", TrackTypeToStr(aTrack),
+                                     decoder.mNumOfConsecutiveError);
+    decoder.Flush();
+    if (aTrack == TrackType::kVideoTrack) {
+      SkipVideoDemuxToNextKeyFrame(decoder.mLastSampleTime.refOr(TimeUnit()));
+    }
+    return;
+  }
+
   bool needInput = NeedInput(decoder);
 
   LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u waiting:%d ahead:%d sid:%u",
        TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
        decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
        uint32_t(size_t(decoder.mSizeOfQueue)), uint32_t(decoder.mOutput.Length()),
        decoder.mWaitingForData, !decoder.HasPromise(), decoder.mLastStreamSourceID);
 
@@ -1361,21 +1377,21 @@ MediaFormatReader::InputExhausted(TrackT
 {
   RefPtr<nsIRunnable> task =
     NewRunnableMethod<TrackType>(
       this, &MediaFormatReader::NotifyInputExhausted, aTrack);
   OwnerThread()->Dispatch(task.forget());
 }
 
 void
-MediaFormatReader::Error(TrackType aTrack)
+MediaFormatReader::Error(TrackType aTrack, MediaDataDecoderError aError)
 {
   RefPtr<nsIRunnable> task =
-    NewRunnableMethod<TrackType>(
-      this, &MediaFormatReader::NotifyError, aTrack);
+    NewRunnableMethod<TrackType, MediaDataDecoderError>(
+      this, &MediaFormatReader::NotifyError, aTrack, aError);
   OwnerThread()->Dispatch(task.forget());
 }
 
 void
 MediaFormatReader::Reset(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Reset(%s) BEGIN", TrackTypeToStr(aTrack));
@@ -1422,18 +1438,18 @@ MediaFormatReader::OnVideoSkipCompleted(
   MOZ_ASSERT(OnTaskQueue());
   LOG("Skipping succeeded, skipped %u frames", aSkipped);
   mSkipRequest.Complete();
   if (mDecoder) {
     mDecoder->NotifyDecodedFrames(aSkipped, 0, aSkipped);
   }
   mVideo.mNumSamplesSkippedTotal += aSkipped;
   mVideo.mNumSamplesSkippedTotalSinceTelemetry += aSkipped;
-  MOZ_ASSERT(!mVideo.mError); // We have flushed the decoder, no frame could
-                              // have been decoded (and as such errored)
+  MOZ_ASSERT(mVideo.mError.isNothing()); // We have flushed the decoder, no frame could
+                                         // have been decoded (and as such errored)
   NotifyDecodingRequested(TrackInfo::kVideoTrack);
 }
 
 void
 MediaFormatReader::OnVideoSkipFailed(MediaTrackDemuxer::SkipFailureHolder aFailure)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Skipping failed, skipped %u frames", aFailure.mSkipped);
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -150,31 +150,31 @@ private:
   // the first sample past the target will be dropped.
   void InternalSeek(TrackType aTrack, const InternalSeekTarget& aTarget);
 
   // Drain the current decoder.
   void DrainDecoder(TrackType aTrack);
   void NotifyNewOutput(TrackType aTrack, MediaData* aSample);
   void NotifyInputExhausted(TrackType aTrack);
   void NotifyDrainComplete(TrackType aTrack);
-  void NotifyError(TrackType aTrack);
+  void NotifyError(TrackType aTrack, MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR);
   void NotifyWaitingForData(TrackType aTrack);
   void NotifyEndOfStream(TrackType aTrack);
   void NotifyDecodingRequested(TrackType aTrack);
 
   void ExtractCryptoInitData(nsTArray<uint8_t>& aInitData);
 
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
   // DecoderCallback proxies the MediaDataDecoderCallback calls to these
   // functions.
   void Output(TrackType aType, MediaData* aSample);
   void InputExhausted(TrackType aTrack);
-  void Error(TrackType aTrack);
+  void Error(TrackType aTrack, MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR);
   void Reset(TrackType aTrack);
   void DrainComplete(TrackType aTrack);
 
   bool ShouldSkip(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold);
   void ResetDemuxers();
 
   size_t SizeOfQueue(TrackType aTrack);
 
@@ -188,18 +188,18 @@ private:
     {
     }
     void Output(MediaData* aSample) override {
       mReader->Output(mType, aSample);
     }
     void InputExhausted() override {
       mReader->InputExhausted(mType);
     }
-    void Error() override {
-      mReader->Error(mType);
+    void Error(MediaDataDecoderError aError) override {
+      mReader->Error(mType, aError);
     }
     void DrainComplete() override {
       mReader->DrainComplete(mType);
     }
     void ReleaseMediaResources() override {
       mReader->ReleaseMediaResources();
     }
     bool OnReaderTaskQueue() override {
@@ -209,35 +209,37 @@ private:
   private:
     MediaFormatReader* mReader;
     TrackType mType;
   };
 
   struct DecoderData {
     DecoderData(MediaFormatReader* aOwner,
                 MediaData::Type aType,
-                uint32_t aDecodeAhead)
+                uint32_t aDecodeAhead,
+                uint32_t aNumOfMaxError)
       : mOwner(aOwner)
       , mType(aType)
       , mMonitor("DecoderData")
       , mDescription("shutdown")
       , mDecodeAhead(aDecodeAhead)
       , mUpdateScheduled(false)
       , mDemuxEOS(false)
       , mWaitingForData(false)
       , mReceivedNewData(false)
       , mDiscontinuity(true)
       , mDecoderInitialized(false)
       , mDecodingRequested(false)
       , mOutputRequested(false)
       , mInputExhausted(false)
-      , mError(false)
       , mNeedDraining(false)
       , mDraining(false)
       , mDrainComplete(false)
+      , mNumOfConsecutiveError(0)
+      , mMaxConsecutiveError(aNumOfMaxError)
       , mNumSamplesInput(0)
       , mNumSamplesOutput(0)
       , mNumSamplesOutputTotal(0)
       , mNumSamplesSkippedTotal(0)
       , mNumSamplesOutputTotalSinceTelemetry(0)
       , mNumSamplesSkippedTotalSinceTelemetry(0)
       , mSizeOfQueue(0)
       , mIsHardwareAccelerated(false)
@@ -295,20 +297,32 @@ private:
     MozPromiseRequestHolder<MediaDataDecoder::InitPromise> mInitPromise;
     // False when decoder is created. True when decoder Init() promise is resolved.
     bool mDecoderInitialized;
     // Set when decoding can proceed. It is reset when a decoding promise is
     // rejected or prior a seek operation.
     bool mDecodingRequested;
     bool mOutputRequested;
     bool mInputExhausted;
-    bool mError;
     bool mNeedDraining;
     bool mDraining;
     bool mDrainComplete;
+
+    uint32_t mNumOfConsecutiveError;
+    uint32_t mMaxConsecutiveError;
+
+    Maybe<MediaDataDecoderError> mError;
+    bool HasFatalError()
+    {
+      if (mError.isSome() && mError.ref() == MediaDataDecoderError::FATAL_ERROR) {
+        return true;
+      }
+      return false;
+    }
+
     // 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::TimeUnit> mLastSampleTime;
 
     // Decoded samples returned my mDecoder awaiting being returned to
@@ -379,16 +393,19 @@ private:
       mDrainComplete = false;
       mTimeThreshold.reset();
       mLastSampleTime.reset();
       mOutput.Clear();
       mNumSamplesInput = 0;
       mNumSamplesOutput = 0;
       mSizeOfQueue = 0;
       mNextStreamSourceID.reset();
+      if (mError.isSome() && mError.ref() == MediaDataDecoderError::DECODE_ERROR) {
+        mError.reset();
+      }
     }
 
     bool HasInternalSeekPending() const
     {
       return mTimeThreshold && !mTimeThreshold.ref().mHasSeeked;
     }
 
     // Used by the MDSM for logging purposes.
@@ -403,18 +420,19 @@ private:
     Maybe<media::TimeUnit> mLastTimeRangesEnd;
     RefPtr<SharedTrackInfo> mInfo;
   };
 
   class DecoderDataWithPromise : public DecoderData {
   public:
     DecoderDataWithPromise(MediaFormatReader* aOwner,
                            MediaData::Type aType,
-                           uint32_t aDecodeAhead)
-      : DecoderData(aOwner, aType, aDecodeAhead)
+                           uint32_t aDecodeAhead,
+                           uint32_t aNumOfMaxError)
+      : DecoderData(aOwner, aType, aDecodeAhead, aNumOfMaxError)
       , mHasPromise(false)
 
     {}
 
     bool HasPromise() const override
     {
       return mHasPromise;
     }
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -106,29 +106,34 @@ protected:
   // This is called on the decode task queue.
   virtual already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
                      FlushableTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      DecoderDoctorDiagnostics* aDiagnostics) = 0;
 };
 
+enum MediaDataDecoderError {
+  FATAL_ERROR,
+  DECODE_ERROR
+};
+
 // A callback used by MediaDataDecoder to return output/errors to the
 // MediaFormatReader.
 // Implementation is threadsafe, and can be called on any thread.
 class MediaDataDecoderCallback {
 public:
   virtual ~MediaDataDecoderCallback() {}
 
   // Called by MediaDataDecoder when a sample has been decoded.
   virtual void Output(MediaData* aData) = 0;
 
   // Denotes an error in the decoding process. The reader will stop calling
   // the decoder.
-  virtual void Error() = 0;
+  virtual void Error(MediaDataDecoderError aError) = 0;
 
   // Denotes that the last input sample has been inserted into the decoder,
   // and no more output can be produced unless more input is sent.
   virtual void InputExhausted() = 0;
 
   virtual void DrainComplete() = 0;
 
   virtual void ReleaseMediaResources() {};
--- a/dom/media/platforms/agnostic/OpusDecoder.cpp
+++ b/dom/media/platforms/agnostic/OpusDecoder.cpp
@@ -137,82 +137,89 @@ OpusDataDecoder::Input(MediaRawData* aSa
                          RefPtr<MediaRawData>(aSample)));
 
   return NS_OK;
 }
 
 void
 OpusDataDecoder::Decode(MediaRawData* aSample)
 {
-  if (DoDecode(aSample) == -1) {
-    mCallback->Error();
-  } else if(mTaskQueue->IsEmpty()) {
-    mCallback->InputExhausted();
+  DecodeError err = DoDecode(aSample);
+  if (err == DecodeError::FATAL_ERROR) {
+    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+  } else {
+    if (err == DecodeError::DECODE_ERROR) { 
+      mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    }
+    if (mTaskQueue->IsEmpty()) {
+      mCallback->InputExhausted();
+    }
   }
 }
 
-int
+OpusDataDecoder::DecodeError
 OpusDataDecoder::DoDecode(MediaRawData* aSample)
 {
   int64_t aDiscardPadding = 0;
   if (aSample->mExtraData) {
     aDiscardPadding = BigEndian::readInt64(aSample->mExtraData->Elements());
   }
   uint32_t channels = mOpusParser->mChannels;
 
   if (mPaddingDiscarded) {
     // Discard padding should be used only on the final packet, so
     // decoding after a padding discard is invalid.
+    // Send fatal error to make sure reader rejects this file.
     OPUS_DEBUG("Opus error, discard padding on interstitial packet");
-    return -1;
+    return FATAL_ERROR;
   }
 
   if (!mLastFrameTime || mLastFrameTime.ref() != aSample->mTime) {
     // We are starting a new block.
     mFrames = 0;
     mLastFrameTime = Some(aSample->mTime);
   }
 
   // Maximum value is 63*2880, so there's no chance of overflow.
   int32_t frames_number = opus_packet_get_nb_frames(aSample->Data(),
                                                     aSample->Size());
   if (frames_number <= 0) {
     OPUS_DEBUG("Invalid packet header: r=%ld length=%ld",
                frames_number, aSample->Size());
-    return -1;
+    return DECODE_ERROR;
   }
 
   int32_t samples = opus_packet_get_samples_per_frame(aSample->Data(),
                                            opus_int32(mOpusParser->mRate));
 
 
   // A valid Opus packet must be between 2.5 and 120 ms long (48kHz).
   int32_t frames = frames_number*samples;
   if (frames < 120 || frames > 5760) {
     OPUS_DEBUG("Invalid packet frames: %ld", frames);
-    return -1;
+    return DECODE_ERROR;
   }
 
   AlignedAudioBuffer buffer(frames * channels);
   if (!buffer) {
-    return -1;
+    return DECODE_ERROR;
   }
 
   // Decode to the appropriate sample type.
 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
   int ret = opus_multistream_decode_float(mOpusDecoder,
                                           aSample->Data(), aSample->Size(),
                                           buffer.get(), frames, false);
 #else
   int ret = opus_multistream_decode(mOpusDecoder,
                                     aSample->Data(), aSample->Size(),
                                     buffer.get(), frames, false);
 #endif
   if (ret < 0) {
-    return -1;
+    return DECODE_ERROR;
   }
   NS_ASSERTION(ret == frames, "Opus decoded too few audio samples");
   CheckedInt64 startTime = aSample->mTime;
 
   // Trim the initial frames while the decoder is settling.
   if (mSkip > 0) {
     int32_t skipFrames = std::min<int32_t>(mSkip, frames);
     int32_t keepFrames = frames - skipFrames;
@@ -222,32 +229,34 @@ OpusDataDecoder::DoDecode(MediaRawData* 
             keepFrames * channels);
     startTime = startTime + FramesToUsecs(skipFrames, mOpusParser->mRate);
     frames = keepFrames;
     mSkip -= skipFrames;
   }
 
   if (aDiscardPadding < 0) {
     // Negative discard padding is invalid.
+    // Send fatal error to make sure reader rejects this file.
     OPUS_DEBUG("Opus error, negative discard padding");
-    return -1;
+    return FATAL_ERROR;
   }
   if (aDiscardPadding > 0) {
     OPUS_DEBUG("OpusDecoder discardpadding %" PRId64 "", aDiscardPadding);
     CheckedInt64 discardFrames =
       TimeUnitToFrames(media::TimeUnit::FromNanoseconds(aDiscardPadding),
                        mOpusParser->mRate);
     if (!discardFrames.isValid()) {
       NS_WARNING("Int overflow in DiscardPadding");
-      return -1;
+      return DECODE_ERROR;
     }
     if (discardFrames.value() > frames) {
       // Discarding more than the entire packet is invalid.
+      // Send fatal error to make sure reader rejects this file.
       OPUS_DEBUG("Opus error, discard padding larger than packet");
-      return -1;
+      return FATAL_ERROR;
     }
     OPUS_DEBUG("Opus decoder discarding %d of %d frames",
         int32_t(discardFrames.value()), frames);
     // Padding discard is only supposed to happen on the final packet.
     // Record the discard so we can return an error if another packet is
     // decoded.
     mPaddingDiscarded = true;
     int32_t keepFrames = frames - discardFrames.value();
@@ -272,35 +281,35 @@ OpusDataDecoder::DoDecode(MediaRawData* 
       buffer[i] = static_cast<AudioDataValue>(MOZ_CLIP_TO_15(val));
     }
   }
 #endif
 
   CheckedInt64 duration = FramesToUsecs(frames, mOpusParser->mRate);
   if (!duration.isValid()) {
     NS_WARNING("OpusDataDecoder: Int overflow converting WebM audio duration");
-    return -1;
+    return DECODE_ERROR;
   }
   CheckedInt64 time =
     startTime - FramesToUsecs(mOpusParser->mPreSkip, mOpusParser->mRate) +
     FramesToUsecs(mFrames, mOpusParser->mRate);
   if (!time.isValid()) {
     NS_WARNING("OpusDataDecoder: Int overflow shifting tstamp by codec delay");
-    return -1;
+    return DECODE_ERROR;
   };
 
   mCallback->Output(new AudioData(aSample->mOffset,
                                   time.value(),
                                   duration.value(),
                                   frames,
                                   Move(buffer),
                                   mOpusParser->mChannels,
                                   mOpusParser->mRate));
   mFrames += frames;
-  return frames;
+  return DECODE_SUCCESS;
 }
 
 void
 OpusDataDecoder::DoDrain()
 {
   mCallback->DrainComplete();
 }
 
--- a/dom/media/platforms/agnostic/OpusDecoder.h
+++ b/dom/media/platforms/agnostic/OpusDecoder.h
@@ -31,20 +31,26 @@ public:
   {
     return "opus audio decoder";
   }
 
   // Return true if mimetype is Opus
   static bool IsOpus(const nsACString& aMimeType);
 
 private:
+  enum DecodeError {
+    DECODE_SUCCESS,
+    DECODE_ERROR,
+    FATAL_ERROR
+  };
+
   nsresult DecodeHeader(const unsigned char* aData, size_t aLength);
 
   void Decode (MediaRawData* aSample);
-  int DoDecode (MediaRawData* aSample);
+  DecodeError DoDecode (MediaRawData* aSample);
   void DoDrain ();
 
   const AudioInfo& mInfo;
   RefPtr<FlushableTaskQueue> mTaskQueue;
   MediaDataDecoderCallback* mCallback;
 
   // Opus decoder state
   nsAutoPtr<OpusParser> mOpusParser;
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -172,17 +172,17 @@ VPXDecoder::DoDecodeFrame(MediaRawData* 
   }
   return 0;
 }
 
 void
 VPXDecoder::DecodeFrame(MediaRawData* aSample)
 {
   if (DoDecodeFrame(aSample) == -1) {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
   } else if (mTaskQueue->IsEmpty()) {
     mCallback->InputExhausted();
   }
 }
 
 nsresult
 VPXDecoder::Input(MediaRawData* aSample)
 {
--- a/dom/media/platforms/agnostic/VorbisDecoder.cpp
+++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp
@@ -135,17 +135,17 @@ VorbisDataDecoder::Input(MediaRawData* a
 
   return NS_OK;
 }
 
 void
 VorbisDataDecoder::Decode(MediaRawData* aSample)
 {
   if (DoDecode(aSample) == -1) {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
   } else if (mTaskQueue->IsEmpty()) {
     mCallback->InputExhausted();
   }
 }
 
 int
 VorbisDataDecoder::DoDecode(MediaRawData* aSample)
 {
--- a/dom/media/platforms/agnostic/WAVDecoder.cpp
+++ b/dom/media/platforms/agnostic/WAVDecoder.cpp
@@ -76,17 +76,17 @@ WaveDataDecoder::Input(MediaRawData* aSa
 
   return NS_OK;
 }
 
 void
 WaveDataDecoder::Decode(MediaRawData* aSample)
 {
   if (!DoDecode(aSample)) {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
   } else if (mTaskQueue->IsEmpty()) {
     mCallback->InputExhausted();
   }
 }
 
 bool
 WaveDataDecoder::DoDecode(MediaRawData* aSample)
 {
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -82,17 +82,17 @@ public:
 
     if (aDecrypted.mStatus == GMPNoKeyErr) {
       // Key became unusable after we sent the sample to CDM to decrypt.
       // Call Input() again, so that the sample is enqueued for decryption
       // if the key becomes usable again.
       Input(aDecrypted.mSample);
     } else if (GMP_FAILED(aDecrypted.mStatus)) {
       if (mCallback) {
-        mCallback->Error();
+        mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
       }
     } else {
       MOZ_ASSERT(!mIsShutdown);
       // The Adobe GMP AAC decoder gets confused if we pass it non-encrypted
       // samples with valid crypto data. So clear the crypto data, since the
       // sample should be decrypted now anyway. If we don't do this and we're
       // using the Adobe GMP for unencrypted decoding of data that is decrypted
       // by gmp-clearkey, decoding will fail.
--- a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
@@ -26,56 +26,56 @@ bool IsOnGMPThread()
 
 void
 AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   if (aRate == 0 || aChannels == 0) {
     NS_WARNING("Invalid rate or num channels returned on GMP audio samples");
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     return;
   }
 
   size_t numFrames = aPCM.Length() / aChannels;
   MOZ_ASSERT((aPCM.Length() % aChannels) == 0);
   AlignedAudioBuffer audioData(aPCM.Length());
   if (!audioData) {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     return;
   }
 
   for (size_t i = 0; i < aPCM.Length(); ++i) {
     audioData[i] = AudioSampleToFloat(aPCM[i]);
   }
 
   if (mMustRecaptureAudioPosition) {
     mAudioFrameSum = 0;
     auto timestamp = UsecsToFrames(aTimeStamp, aRate);
     if (!timestamp.isValid()) {
       NS_WARNING("Invalid timestamp");
-      mCallback->Error();
+      mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
       return;
     }
     mAudioFrameOffset = timestamp.value();
     mMustRecaptureAudioPosition = false;
   }
 
   auto timestamp = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, aRate);
   if (!timestamp.isValid()) {
     NS_WARNING("Invalid timestamp on audio samples");
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     return;
   }
   mAudioFrameSum += numFrames;
 
   auto duration = FramesToUsecs(numFrames, aRate);
   if (!duration.isValid()) {
     NS_WARNING("Invalid duration on audio samples");
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     return;
   }
 
   RefPtr<AudioData> audio(new AudioData(mLastStreamOffset,
                                         timestamp.value(),
                                         duration.value(),
                                         numFrames,
                                         Move(audioData),
@@ -111,24 +111,24 @@ AudioCallbackAdapter::ResetComplete()
   mMustRecaptureAudioPosition = true;
   mCallback->FlushComplete();
 }
 
 void
 AudioCallbackAdapter::Error(GMPErr aErr)
 {
   MOZ_ASSERT(IsOnGMPThread());
-  mCallback->Error();
+  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
 }
 
 void
 AudioCallbackAdapter::Terminated()
 {
   NS_WARNING("AAC GMP decoder terminated.");
-  mCallback->Error();
+  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
 }
 
 void
 GMPAudioDecoder::InitTags(nsTArray<nsCString>& aTags)
 {
   aTags.AppendElement(NS_LITERAL_CSTRING("aac"));
   const Maybe<nsCString> gmp(
     GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("audio/mp4a-latm")));
@@ -200,26 +200,26 @@ GMPAudioDecoder::Init()
 
 nsresult
 GMPAudioDecoder::Input(MediaRawData* aSample)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   RefPtr<MediaRawData> sample(aSample);
   if (!mGMP) {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
     return NS_ERROR_FAILURE;
   }
 
   mAdapter->SetLastStreamOffset(sample->mOffset);
 
   gmp::GMPAudioSamplesImpl samples(sample, mConfig.mChannels, mConfig.mRate);
   nsresult rv = mGMP->Decode(samples);
   if (NS_FAILED(rv)) {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
 GMPAudioDecoder::Flush()
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -47,17 +47,17 @@ VideoCallbackAdapter::Decoded(GMPVideoi4
                                             decodedFrame->Duration(),
                                             b,
                                             false,
                                             -1,
                                             pictureRegion);
   if (v) {
     mCallback->Output(v);
   } else {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
   }
 }
 
 void
 VideoCallbackAdapter::ReceivedDecodedReferenceFrame(const uint64_t aPictureId)
 {
   MOZ_ASSERT(IsOnGMPThread());
 }
@@ -88,25 +88,25 @@ VideoCallbackAdapter::ResetComplete()
   MOZ_ASSERT(IsOnGMPThread());
   mCallback->FlushComplete();
 }
 
 void
 VideoCallbackAdapter::Error(GMPErr aErr)
 {
   MOZ_ASSERT(IsOnGMPThread());
-  mCallback->Error();
+  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
 }
 
 void
 VideoCallbackAdapter::Terminated()
 {
   // Note that this *may* be called from the proxy thread also.
   NS_WARNING("H.264 GMP decoder terminated.");
-  mCallback->Error();
+  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
 }
 
 void
 GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags)
 {
   aTags.AppendElement(NS_LITERAL_CSTRING("h264"));
   const Maybe<nsCString> gmp(
     GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("video/avc")));
@@ -122,24 +122,24 @@ GMPVideoDecoder::GetNodeId()
 }
 
 GMPUniquePtr<GMPVideoEncodedFrame>
 GMPVideoDecoder::CreateFrame(MediaRawData* aSample)
 {
   GMPVideoFrame* ftmp = nullptr;
   GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
   if (GMP_FAILED(err)) {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
     return nullptr;
   }
 
   GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp));
   err = frame->CreateEmptyFrame(aSample->Size());
   if (GMP_FAILED(err)) {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
     return nullptr;
   }
 
   memcpy(frame->Buffer(), aSample->Data(), frame->Size());
 
   // Convert 4-byte NAL unit lengths to host-endian 4-byte buffer lengths to
   // suit the GMP API.
   if (mConvertNALUnitLengths) {
@@ -244,31 +244,31 @@ GMPVideoDecoder::Init()
 
 nsresult
 GMPVideoDecoder::Input(MediaRawData* aSample)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   RefPtr<MediaRawData> sample(aSample);
   if (!mGMP) {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
     return NS_ERROR_FAILURE;
   }
 
   mAdapter->SetLastStreamOffset(sample->mOffset);
 
   GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample);
   if (!frame) {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     return NS_ERROR_FAILURE;
   }
   nsTArray<uint8_t> info; // No codec specific per-frame info to pass.
   nsresult rv = mGMP->Decode(Move(frame), false, info, 0);
   if (NS_FAILED(rv)) {
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
 GMPVideoDecoder::Flush()
--- a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
@@ -5,19 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaDataDecoderProxy.h"
 #include "MediaData.h"
 
 namespace mozilla {
 
 void
-MediaDataDecoderCallbackProxy::Error()
+MediaDataDecoderCallbackProxy::Error(MediaDataDecoderError aError)
 {
-  mProxyCallback->Error();
+  mProxyCallback->Error(aError);
 }
 
 void
 MediaDataDecoderCallbackProxy::FlushComplete()
 {
   mProxyDecoder->FlushComplete();
 }
 
--- a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
@@ -69,17 +69,17 @@ public:
    , mProxyCallback(aCallback)
   {
   }
 
   void Output(MediaData* aData) override {
     mProxyCallback->Output(aData);
   }
 
-  void Error() override;
+  void Error(MediaDataDecoderError aError) override;
 
   void InputExhausted() override {
     mProxyCallback->InputExhausted();
   }
 
   void DrainComplete() override {
     mProxyCallback->DrainComplete();
   }
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -373,17 +373,17 @@ MediaCodecDataDecoder::Init()
                MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__);
 }
 
 nsresult
 MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface)
 {
   mDecoder = CreateDecoder(mMimeType);
   if (!mDecoder) {
-    INVOKE_CALLBACK(Error);
+    INVOKE_CALLBACK(Error, MediaDataDecoderError::FATAL_ERROR);
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv;
   NS_ENSURE_SUCCESS(rv = mDecoder->Configure(mFormat, aSurface, nullptr, 0), rv);
   NS_ENSURE_SUCCESS(rv = mDecoder->Start(), rv);
 
   NS_ENSURE_SUCCESS(rv = ResetInputBuffers(), rv);
@@ -400,17 +400,17 @@ static const int64_t kDecoderTimeout = 1
 
 #define BREAK_ON_DECODER_ERROR() \
   if (NS_FAILED(res)) { \
     NS_WARNING("Exiting decoder loop due to exception"); \
     if (State() == kDrainDecoder) { \
       INVOKE_CALLBACK(DrainComplete); \
       State(kDecoding); \
     } \
-    INVOKE_CALLBACK(Error); \
+    INVOKE_CALLBACK(Error, MediaDataDecoderError::FATAL_ERROR); \
     break; \
   }
 
 nsresult
 MediaCodecDataDecoder::GetInputBuffer(
     JNIEnv* aEnv, int aIndex, jni::Object::LocalRef* aBuffer)
 {
   MOZ_ASSERT(aEnv);
@@ -631,17 +631,17 @@ MediaCodecDataDecoder::DecoderLoop()
     } else if (outputStatus == MediaCodec::INFO_OUTPUT_BUFFERS_CHANGED) {
       res = ResetOutputBuffers();
       BREAK_ON_DECODER_ERROR();
     } else if (outputStatus == MediaCodec::INFO_OUTPUT_FORMAT_CHANGED) {
       res = mDecoder->GetOutputFormat(ReturnTo(&outputFormat));
       BREAK_ON_DECODER_ERROR();
     } else if (outputStatus < 0) {
       NS_WARNING("Unknown error from decoder!");
-      INVOKE_CALLBACK(Error);
+      INVOKE_CALLBACK(Error, MediaDataDecoderError::DECODE_ERROR);
       // Don't break here just in case it's recoverable. If it's not, other
       // stuff will fail later and we'll bail out.
     } else {
       // We have a valid buffer index >= 0 here.
       int32_t flags;
       nsresult res = bufferInfo->Flags(&flags);
       BREAK_ON_DECODER_ERROR();
 
--- a/dom/media/platforms/apple/AppleATDecoder.cpp
+++ b/dom/media/platforms/apple/AppleATDecoder.cpp
@@ -172,28 +172,28 @@ static OSStatus
 
 void
 AppleATDecoder::SubmitSample(MediaRawData* aSample)
 {
   nsresult rv = NS_OK;
   if (!mConverter) {
     rv = SetupDecoder(aSample);
     if (rv != NS_OK && rv != NS_ERROR_NOT_INITIALIZED) {
-      mCallback->Error();
+      mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
       return;
     }
   }
 
   mQueuedSamples.AppendElement(aSample);
 
   if (rv == NS_OK) {
     for (size_t i = 0; i < mQueuedSamples.Length(); i++) {
       if (NS_FAILED(DecodeSample(mQueuedSamples[i]))) {
         mQueuedSamples.Clear();
-        mCallback->Error();
+        mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
         return;
       }
     }
     mQueuedSamples.Clear();
   }
 
   if (mTaskQueue->IsEmpty()) {
     mCallback->InputExhausted();
--- a/dom/media/platforms/apple/AppleVDADecoder.cpp
+++ b/dom/media/platforms/apple/AppleVDADecoder.cpp
@@ -338,17 +338,17 @@ AppleVDADecoder::OutputFrame(CVPixelBuff
     MOZ_ASSERT(planes == 2, "Likely not NV12 format and it must be.");
 
     VideoData::YCbCrBuffer buffer;
 
     // Lock the returned image data.
     CVReturn rv = CVPixelBufferLockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
     if (rv != kCVReturnSuccess) {
       NS_ERROR("error locking pixel data");
-      mCallback->Error();
+      mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
       return NS_ERROR_FAILURE;
     }
     // Y plane.
     buffer.mPlanes[0].mData =
       static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(aImage, 0));
     buffer.mPlanes[0].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 0);
     buffer.mPlanes[0].mWidth = width;
     buffer.mPlanes[0].mHeight = height;
@@ -406,17 +406,17 @@ AppleVDADecoder::OutputFrame(CVPixelBuff
                                  visible);
 #else
     MOZ_ASSERT_UNREACHABLE("No MacIOSurface on iOS");
 #endif
   }
 
   if (!data) {
     NS_ERROR("Couldn't create VideoData for frame");
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
     return NS_ERROR_FAILURE;
   }
 
   // Frames come out in DTS order but we need to output them
   // in composition order.
   MonitorAutoLock mon(mMonitor);
   mReorderQueue.Push(data);
   while (mReorderQueue.Length() > mMaxRefFrames) {
@@ -489,17 +489,17 @@ AppleVDADecoder::SubmitFrame(MediaRawDat
 
   OSStatus rv = VDADecoderDecode(mDecoder,
                                  0,
                                  block,
                                  frameInfo);
 
   if (rv != noErr) {
     NS_WARNING("AppleVDADecoder: Couldn't pass frame to decoder");
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
     return NS_ERROR_FAILURE;
   }
 
   if (mIs106) {
     // TN2267:
     // frameInfo: A CFDictionaryRef containing information to be returned in
     // the output callback for this frame.
     // This dictionary can contain client provided information associated with
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -243,17 +243,17 @@ AppleVTDecoder::SubmitFrame(MediaRawData
   rv = VTDecompressionSessionDecodeFrame(mSession,
                                          sample,
                                          decodeFlags,
                                          CreateAppleFrameRef(aSample),
                                          &infoFlags);
   if (rv != noErr && !(infoFlags & kVTDecodeInfo_FrameDropped)) {
     LOG("AppleVTDecoder: Error %d VTDecompressionSessionDecodeFrame", rv);
     NS_WARNING("Couldn't pass frame to decoder");
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     return NS_ERROR_FAILURE;
   }
 
   // Ask for more data.
   if (!mInputIncoming && mQueuedSamples <= mMaxRefFrames) {
     LOG("AppleVTDecoder task queue empty; requesting more data");
     mCallback->InputExhausted();
   }
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
@@ -103,67 +103,67 @@ FFmpegAudioDecoder<LIBAV_VER>::DecodePac
   AVPacket packet;
   mLib->av_init_packet(&packet);
 
   packet.data = const_cast<uint8_t*>(aSample->Data());
   packet.size = aSample->Size();
 
   if (!PrepareFrame()) {
     NS_WARNING("FFmpeg audio decoder failed to allocate frame.");
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     return;
   }
 
   int64_t samplePosition = aSample->mOffset;
   media::TimeUnit pts = media::TimeUnit::FromMicroseconds(aSample->mTime);
 
   while (packet.size > 0) {
     int decoded;
     int bytesConsumed =
       mLib->avcodec_decode_audio4(mCodecContext, mFrame, &decoded, &packet);
 
     if (bytesConsumed < 0) {
       NS_WARNING("FFmpeg audio decoder error.");
-      mCallback->Error();
+      mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
       return;
     }
 
     if (decoded) {
       uint32_t numChannels = mCodecContext->channels;
       AudioConfig::ChannelLayout layout(numChannels);
       if (!layout.IsValid()) {
-        mCallback->Error();
+        mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
         return;
       }
 
       uint32_t samplingRate = mCodecContext->sample_rate;
 
       AlignedAudioBuffer audio =
         CopyAndPackAudio(mFrame, numChannels, mFrame->nb_samples);
 
       media::TimeUnit duration =
         FramesToTimeUnit(mFrame->nb_samples, samplingRate);
       if (!audio || !duration.IsValid()) {
         NS_WARNING("Invalid count of accumulated audio samples");
-        mCallback->Error();
+        mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
         return;
       }
 
       RefPtr<AudioData> data = new AudioData(samplePosition,
                                              pts.ToMicroseconds(),
                                              duration.ToMicroseconds(),
                                              mFrame->nb_samples,
                                              Move(audio),
                                              numChannels,
                                              samplingRate);
       mCallback->Output(data);
       pts += duration;
       if (!pts.IsValid()) {
         NS_WARNING("Invalid count of accumulated audio samples");
-        mCallback->Error();
+        mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
         return;
       }
     }
     packet.data += bytesConsumed;
     packet.size -= bytesConsumed;
     samplePosition += bytesConsumed;
   }
 
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
@@ -176,17 +176,17 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeF
     while (inputSize) {
       uint8_t* data;
       int size;
       int len = mLib->av_parser_parse2(mCodecParser, mCodecContext, &data, &size,
                                        inputData, inputSize,
                                        aSample->mTime, aSample->mTimecode,
                                        aSample->mOffset);
       if (size_t(len) > inputSize) {
-        mCallback->Error();
+        mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
         return DecodeResult::DECODE_ERROR;
       }
       inputData += len;
       inputSize -= len;
       if (size) {
         switch (DoDecodeFrame(aSample, data, size)) {
           case DecodeResult::DECODE_ERROR:
             return DecodeResult::DECODE_ERROR;
@@ -224,17 +224,17 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeF
   // (FFmpeg >= 1.0 provides av_frame_get_pkt_duration)
   // As such we instead use a map using the dts as key that we will retrieve
   // later.
   // The map will have a typical size of 16 entry.
   mDurationMap.Insert(aSample->mTimecode, aSample->mDuration);
 
   if (!PrepareFrame()) {
     NS_WARNING("FFmpeg h264 decoder failed to allocate frame.");
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     return DecodeResult::DECODE_ERROR;
   }
 
   // Required with old version of FFmpeg/LibAV
   mFrame->reordered_opaque = AV_NOPTS_VALUE;
 
   int decoded;
   int bytesConsumed =
@@ -243,17 +243,17 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeF
   FFMPEG_LOG("DoDecodeFrame:decode_video: rv=%d decoded=%d "
              "(Input: pts(%lld) dts(%lld) Output: pts(%lld) "
              "opaque(%lld) pkt_pts(%lld) pkt_dts(%lld))",
              bytesConsumed, decoded, packet.pts, packet.dts, mFrame->pts,
              mFrame->reordered_opaque, mFrame->pkt_pts, mFrame->pkt_dts);
 
   if (bytesConsumed < 0) {
     NS_WARNING("FFmpeg video decoder error.");
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     return DecodeResult::DECODE_ERROR;
   }
 
   // If we've decoded a frame then we need to output it
   if (decoded) {
     int64_t pts = mPtsContext.GuessCorrectPts(mFrame->pkt_pts, mFrame->pkt_dts);
     // Retrieve duration from dts.
     // We use the first entry found matching this dts (this is done to
@@ -302,17 +302,17 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeF
                                             b,
                                             !!mFrame->key_frame,
                                             -1,
                                             mInfo.ScaledImageRect(mFrame->width,
                                                                   mFrame->height));
 
     if (!v) {
       NS_WARNING("image allocation error.");
-      mCallback->Error();
+      mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
       return DecodeResult::DECODE_ERROR;
     }
     mCallback->Output(v);
     return DecodeResult::DECODE_FRAME;
   }
   return DecodeResult::DECODE_NO_FRAME;
 }
 
--- a/dom/media/platforms/omx/OmxDataDecoder.cpp
+++ b/dom/media/platforms/omx/OmxDataDecoder.cpp
@@ -434,20 +434,20 @@ OmxDataDecoder::EmptyBufferDone(BufferDa
 
 void
 OmxDataDecoder::EmptyBufferFailure(OmxBufferFailureHolder aFailureHolder)
 {
   NotifyError(aFailureHolder.mError, __func__);
 }
 
 void
-OmxDataDecoder::NotifyError(OMX_ERRORTYPE aError, const char* aLine)
+OmxDataDecoder::NotifyError(OMX_ERRORTYPE aOmxError, const char* aLine, MediaDataDecoderError aError)
 {
-  LOG("NotifyError %d at %s", aError, aLine);
-  mCallback->Error();
+  LOG("NotifyError %d (%d) at %s", aOmxError, aError, aLine);
+  mCallback->Error(aError);
 }
 
 void
 OmxDataDecoder::FillAndEmptyBuffers()
 {
   MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
   MOZ_ASSERT(mOmxState == OMX_StateExecuting);
 
@@ -692,16 +692,21 @@ OmxDataDecoder::Event(OMX_EVENTTYPE aEve
         MOZ_ASSERT(mPortSettingsChanged == -1);
         mPortSettingsChanged = aData1;
       }
       LOG("Got OMX_EventPortSettingsChanged event");
       break;
     }
     default:
     {
+      // Got error during decoding, send msg to MFR skipping to next key frame.
+      if (aEvent == OMX_EventError && mOmxState == OMX_StateExecuting) {
+        NotifyError((OMX_ERRORTYPE)aData1, __func__, MediaDataDecoderError::DECODE_ERROR);
+        return true;
+      }
       LOG("WARNING: got none handle event: %d, aData1: %d, aData2: %d",
           aEvent, aData1, aData2);
       return false;
     }
   }
 
   return true;
 }
--- a/dom/media/platforms/omx/OmxDataDecoder.h
+++ b/dom/media/platforms/omx/OmxDataDecoder.h
@@ -96,17 +96,19 @@ protected:
   void FillBufferDone(BufferData* aData);
 
   void FillBufferFailure(OmxBufferFailureHolder aFailureHolder);
 
   void EmptyBufferDone(BufferData* aData);
 
   void EmptyBufferFailure(OmxBufferFailureHolder aFailureHolder);
 
-  void NotifyError(OMX_ERRORTYPE aError, const char* aLine);
+  void NotifyError(OMX_ERRORTYPE aOmxError,
+                   const char* aLine,
+                   MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR);
 
   // Configure audio/video codec.
   // Some codec may just ignore this and rely on codec specific data in
   // FillCodecConfigDataToOmx().
   void ConfigCodec();
 
   // Sending codec specific data to OMX component. OMX component could send a
   // OMX_EventPortSettingsChanged back to client. And then client needs to
--- a/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
+++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
@@ -121,17 +121,17 @@ WMFMediaDataDecoder::ProcessDecode(Media
   if (mIsFlushing) {
     // Skip sample, to be released by runnable.
     return;
   }
 
   HRESULT hr = mMFTManager->Input(aSample);
   if (FAILED(hr)) {
     NS_WARNING("MFTManager rejected sample");
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     if (!mRecordedError) {
       SendTelemetry(hr);
       mRecordedError = true;
     }
     return;
   }
 
   mLastStreamOffset = aSample->mOffset;
@@ -150,17 +150,17 @@ WMFMediaDataDecoder::ProcessOutput()
     mCallback->Output(output);
   }
   if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
     if (mTaskQueue->IsEmpty()) {
       mCallback->InputExhausted();
     }
   } else if (FAILED(hr)) {
     NS_WARNING("WMFMediaDataDecoder failed to output data");
-    mCallback->Error();
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
     if (!mRecordedError) {
       SendTelemetry(hr);
       mRecordedError = true;
     }
   }
 }
 
 void
--- a/dom/media/platforms/wrappers/FuzzingWrapper.cpp
+++ b/dom/media/platforms/wrappers/FuzzingWrapper.cpp
@@ -168,26 +168,29 @@ DecoderCallbackFuzzingWrapper::Output(Me
   }
 
   // Passing the data straight through, no need to dispatch to another queue,
   // callback should deal with that.
   mCallback->Output(aData);
 }
 
 void
-DecoderCallbackFuzzingWrapper::Error()
+DecoderCallbackFuzzingWrapper::Error(MediaDataDecoderError aError)
 {
   if (!mTaskQueue->IsCurrentThreadIn()) {
-    mTaskQueue->Dispatch(NewRunnableMethod(this, &DecoderCallbackFuzzingWrapper::Error));
+    mTaskQueue->Dispatch(
+      NewRunnableMethod<MediaDataDecoderError>(this,
+                                               &DecoderCallbackFuzzingWrapper::Error,
+                                               aError));
     return;
   }
   CFW_LOGV("");
   MOZ_ASSERT(mCallback);
   ClearDelayedOutput();
-  mCallback->Error();
+  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
 }
 
 void
 DecoderCallbackFuzzingWrapper::InputExhausted()
 {
   if (!mTaskQueue->IsCurrentThreadIn()) {
     mTaskQueue->Dispatch(NewRunnableMethod(this, &DecoderCallbackFuzzingWrapper::InputExhausted));
     return;
--- a/dom/media/platforms/wrappers/FuzzingWrapper.h
+++ b/dom/media/platforms/wrappers/FuzzingWrapper.h
@@ -55,17 +55,17 @@ public:
   // in lots of frames being decoded and queued for delayed output!
   void SetDontDelayInputExhausted(bool aDontDelayInputExhausted);
 
 private:
   virtual ~DecoderCallbackFuzzingWrapper();
 
   // MediaDataDecoderCallback implementation.
   void Output(MediaData* aData) override;
-  void Error() override;
+  void Error(MediaDataDecoderError aError) override;
   void InputExhausted() override;
   void DrainComplete() override;
   void ReleaseMediaResources() override;
   bool OnReaderTaskQueue() override;
 
   MediaDataDecoderCallback* mCallback;
 
   // Settings for minimum frame output interval & InputExhausted,
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -186,27 +186,27 @@ H264Converter::CreateDecoderAndInit(Medi
 }
 
 void
 H264Converter::OnDecoderInitDone(const TrackType aTrackType)
 {
   mInitPromiseRequest.Complete();
   for (uint32_t i = 0 ; i < mMediaRawSamples.Length(); i++) {
     if (NS_FAILED(mDecoder->Input(mMediaRawSamples[i]))) {
-      mCallback->Error();
+      mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
     }
   }
   mMediaRawSamples.Clear();
 }
 
 void
 H264Converter::OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason)
 {
   mInitPromiseRequest.Complete();
-  mCallback->Error();
+  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
 }
 
 nsresult
 H264Converter::CheckForSPSChange(MediaRawData* aSample)
 {
   RefPtr<MediaByteBuffer> extra_data =
     mp4_demuxer::AnnexB::ExtractExtraData(aSample);
   if (!mp4_demuxer::AnnexB::HasSPS(extra_data) ||