Backed out 4 changesets (bug 1508434) for mda failures on test_waveShaperPassThrough.html. CLOSED TREE
authorBrindusan Cristian <cbrindusan@mozilla.com>
Wed, 09 Jan 2019 21:00:35 +0200
changeset 510238 ef80c62a30709cfffa64abfa38eb2ad880a906e3
parent 510237 9d80f662ad2bda4696f525d8c12f3a3ce0addf44
child 510239 be5403f2da75a7a61c2b67870096a3dbda885e3e
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1508434
milestone66.0a1
backs out1851290ec29bf3e26de182cf91156dbdd571b3cd
12424313d637e32e06fd12a758d14908d0099a15
8fbed32432174128f2aa82d82b88b5c386f0c52d
25b67aa0ef55c463fb947630f0313b334aff51f8
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
Backed out 4 changesets (bug 1508434) for mda failures on test_waveShaperPassThrough.html. CLOSED TREE Backed out changeset 1851290ec29b (bug 1508434) Backed out changeset 12424313d637 (bug 1508434) Backed out changeset 8fbed3243217 (bug 1508434) Backed out changeset 25b67aa0ef55 (bug 1508434)
dom/media/MediaFormatReader.cpp
dom/media/ipc/PRemoteVideoDecoder.ipdl
dom/media/ipc/PVideoDecoder.ipdl
dom/media/ipc/RemoteVideoDecoderChild.cpp
dom/media/ipc/RemoteVideoDecoderParent.cpp
dom/media/ipc/VideoDecoderChild.cpp
dom/media/ipc/VideoDecoderParent.cpp
dom/media/platforms/PlatformDecoderModule.h
dom/media/platforms/android/RemoteDataDecoder.cpp
dom/media/platforms/android/RemoteDataDecoder.h
dom/media/platforms/apple/AppleVTDecoder.cpp
dom/media/platforms/wmf/WMFMediaDataDecoder.h
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -2884,19 +2884,20 @@ void MediaFormatReader::SetVideoDecodeTh
     // If IsSeeking() is true, then video seek must have completed already.
     TimeUnit keyframe;
     if (NS_FAILED(mVideo.mTrackDemuxer->GetNextRandomAccessPoint(&keyframe))) {
       return;
     }
 
     // If the key frame is invalid/infinite, it means the target position is
     // closing to end of stream. We don't want to skip any frame at this point.
-    threshold = keyframe.IsValid() && !keyframe.IsInfinite()
-                    ? mOriginalSeekTarget.GetTime()
-                    : TimeUnit::Invalid();
+    if (!keyframe.IsValid() || keyframe.IsInfinite()) {
+      return;
+    }
+    threshold = mOriginalSeekTarget.GetTime();
   } else {
     return;
   }
 
   LOG("Set seek threshold to %" PRId64, threshold.ToMicroseconds());
   mVideo.mDecoder->SetSeekThreshold(threshold);
 }
 
--- a/dom/media/ipc/PRemoteVideoDecoder.ipdl
+++ b/dom/media/ipc/PRemoteVideoDecoder.ipdl
@@ -32,17 +32,17 @@ async protocol PRemoteVideoDecoder
 parent:
   async Init();
 
   async Input(MediaRawDataIPDL data);
 
   async Flush();
   async Drain();
   async Shutdown();
-  // To clear the threshold, call with INT64_MIN.
+
   async SetSeekThreshold(int64_t time);
 
   async __delete__();
 
 child:
   async InitComplete(nsCString decoderDescription,
                      ConversionRequired conversion);
   async InitFailed(nsresult reason);
--- a/dom/media/ipc/PVideoDecoder.ipdl
+++ b/dom/media/ipc/PVideoDecoder.ipdl
@@ -32,17 +32,17 @@ async protocol PVideoDecoder
 parent:
   async Init();
 
   async Input(MediaRawDataIPDL data);
 
   async Flush();
   async Drain();
   async Shutdown();
-  // To clear the threshold, call with INT64_MIN.
+
   async SetSeekThreshold(int64_t time);
 
   async __delete__();
 
 child:
   async InitComplete(nsCString decoderDescription, bool hardware, nsCString hardwareReason, uint32_t conversion);
   async InitFailed(nsresult reason);
 
--- a/dom/media/ipc/RemoteVideoDecoderChild.cpp
+++ b/dom/media/ipc/RemoteVideoDecoderChild.cpp
@@ -284,17 +284,17 @@ bool RemoteVideoDecoderChild::IsHardware
 nsCString RemoteVideoDecoderChild::GetDescriptionName() const {
   AssertOnManagerThread();
   return mDescription;
 }
 
 void RemoteVideoDecoderChild::SetSeekThreshold(const media::TimeUnit& aTime) {
   AssertOnManagerThread();
   if (mCanSend) {
-    SendSetSeekThreshold(aTime.IsValid() ? aTime.ToMicroseconds() : INT64_MIN);
+    SendSetSeekThreshold(aTime.ToMicroseconds());
   }
 }
 
 MediaDataDecoder::ConversionRequired RemoteVideoDecoderChild::NeedsConversion()
     const {
   AssertOnManagerThread();
   return mConversion;
 }
--- a/dom/media/ipc/RemoteVideoDecoderParent.cpp
+++ b/dom/media/ipc/RemoteVideoDecoderParent.cpp
@@ -199,19 +199,17 @@ mozilla::ipc::IPCResult RemoteVideoDecod
   mDecoder = nullptr;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult RemoteVideoDecoderParent::RecvSetSeekThreshold(
     const int64_t& aTime) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
-  mDecoder->SetSeekThreshold(aTime == INT64_MIN
-                                 ? TimeUnit::Invalid()
-                                 : TimeUnit::FromMicroseconds(aTime));
+  mDecoder->SetSeekThreshold(TimeUnit::FromMicroseconds(aTime));
   return IPC_OK();
 }
 
 void RemoteVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
   if (mDecoder) {
     mDecoder->Shutdown();
--- a/dom/media/ipc/VideoDecoderChild.cpp
+++ b/dom/media/ipc/VideoDecoderChild.cpp
@@ -299,17 +299,17 @@ bool VideoDecoderChild::IsHardwareAccele
 nsCString VideoDecoderChild::GetDescriptionName() const {
   AssertOnManagerThread();
   return mDescription;
 }
 
 void VideoDecoderChild::SetSeekThreshold(const media::TimeUnit& aTime) {
   AssertOnManagerThread();
   if (mCanSend) {
-    SendSetSeekThreshold(aTime.IsValid() ? aTime.ToMicroseconds() : INT64_MIN);
+    SendSetSeekThreshold(aTime.ToMicroseconds());
   }
 }
 
 MediaDataDecoder::ConversionRequired VideoDecoderChild::NeedsConversion()
     const {
   AssertOnManagerThread();
   return mConversion;
 }
--- a/dom/media/ipc/VideoDecoderParent.cpp
+++ b/dom/media/ipc/VideoDecoderParent.cpp
@@ -247,19 +247,17 @@ mozilla::ipc::IPCResult VideoDecoderPare
   mDecoder = nullptr;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult VideoDecoderParent::RecvSetSeekThreshold(
     const int64_t& aTime) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
-  mDecoder->SetSeekThreshold(aTime == INT64_MIN
-                                 ? TimeUnit::Invalid()
-                                 : TimeUnit::FromMicroseconds(aTime));
+  mDecoder->SetSeekThreshold(TimeUnit::FromMicroseconds(aTime));
   return IPC_OK();
 }
 
 void VideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
   if (mDecoder) {
     mDecoder->Shutdown();
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -314,18 +314,17 @@ class MediaDataDecoder : public DecoderD
   }
 
   // Return the name of the MediaDataDecoder, only used for decoding.
   // May be accessed in a non thread-safe fashion.
   virtual nsCString GetDescriptionName() const = 0;
 
   // Set a hint of seek target time to decoder. Decoder will drop any decoded
   // data which pts is smaller than this value. This threshold needs to be clear
-  // after reset decoder. To clear it explicitly, call this method with
-  // TimeUnit::Invalid().
+  // after reset decoder.
   // Decoder may not honor this value. However, it'd be better that
   // video decoder implements this API to improve seek performance.
   // Note: it should be called before Input() or after Flush().
   virtual void SetSeekThreshold(const media::TimeUnit& aTime) {}
 
   // When playing adaptive playback, recreating an Android video decoder will
   // cause the transition not smooth during resolution change.
   // Reuse the decoder if the decoder support recycling.
--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -27,50 +27,43 @@
 using namespace mozilla;
 using namespace mozilla::gl;
 using namespace mozilla::java;
 using namespace mozilla::java::sdk;
 using media::TimeUnit;
 
 namespace mozilla {
 
-// Hold a reference to the output buffer until we're ready to release it back to
-// the Java codec (for rendering or not).
-class RenderOrReleaseOutput {
+class RemoteVideoDecoder : public RemoteDataDecoder {
  public:
-    RenderOrReleaseOutput(CodecProxy::Param aCodec, Sample::Param aSample)
+  // Hold an output buffer and render it to the surface when the frame is sent
+  // to compositor, or release it if not presented.
+  class RenderOrReleaseOutput : public VideoData::Listener {
+   public:
+    RenderOrReleaseOutput(java::CodecProxy::Param aCodec,
+                          java::Sample::Param aSample)
         : mCodec(aCodec), mSample(aSample) {}
 
-  virtual ~RenderOrReleaseOutput() { ReleaseOutput(false); }
+    ~RenderOrReleaseOutput() { ReleaseOutput(false); }
 
- protected:
+    void OnSentToCompositor() override {
+      ReleaseOutput(true);
+      mCodec = nullptr;
+      mSample = nullptr;
+    }
+
+   private:
     void ReleaseOutput(bool aToRender) {
       if (mCodec && mSample) {
         mCodec->ReleaseOutput(mSample, aToRender);
-      mCodec = nullptr;
-      mSample = nullptr;
       }
     }
 
- private:
-    CodecProxy::GlobalRef mCodec;
-    Sample::GlobalRef mSample;
-  };
-
-class RemoteVideoDecoder : public RemoteDataDecoder {
- public:
-  // Render the output to the surface when the frame is sent
-  // to compositor, or release it if not presented.
-  class CompositeListener : private RenderOrReleaseOutput,
-                            public VideoData::Listener {
-   public:
-    CompositeListener(CodecProxy::Param aCodec, Sample::Param aSample)
-        : RenderOrReleaseOutput(aCodec, aSample) {}
-
-    void OnSentToCompositor() override { ReleaseOutput(true); }
+    java::CodecProxy::GlobalRef mCodec;
+    java::Sample::GlobalRef mSample;
   };
 
   class InputInfo {
    public:
     InputInfo() {}
 
     InputInfo(const int64_t aDurationUs, const gfx::IntSize& aImageSize,
               const gfx::IntSize& aDisplaySize)
@@ -88,18 +81,66 @@ class RemoteVideoDecoder : public Remote
     explicit CallbacksSupport(RemoteVideoDecoder* aDecoder)
         : mDecoder(aDecoder) {}
 
     void HandleInput(int64_t aTimestamp, bool aProcessed) override {
       mDecoder->UpdateInputStatus(aTimestamp, aProcessed);
     }
 
     void HandleOutput(Sample::Param aSample) override {
-      // aSample will be implicitly converted into a GlobalRef.
-      mDecoder->ProcessOutput(std::move(aSample));
+      UniquePtr<VideoData::Listener> releaseSample(
+          new RenderOrReleaseOutput(mDecoder->mJavaDecoder, aSample));
+
+      BufferInfo::LocalRef info = aSample->Info();
+
+      int32_t flags;
+      bool ok = NS_SUCCEEDED(info->Flags(&flags));
+
+      int32_t offset;
+      ok &= NS_SUCCEEDED(info->Offset(&offset));
+
+      int32_t size;
+      ok &= NS_SUCCEEDED(info->Size(&size));
+
+      int64_t presentationTimeUs;
+      ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs));
+
+      if (!ok) {
+        HandleError(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                RESULT_DETAIL("VideoCallBack::HandleOutput")));
+        return;
+      }
+
+      InputInfo inputInfo;
+      ok = mDecoder->mInputInfos.Find(presentationTimeUs, inputInfo);
+      bool isEOS = !!(flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM);
+      if (!ok && !isEOS) {
+        // Ignore output with no corresponding input.
+        return;
+      }
+
+      if (ok && (size > 0 || presentationTimeUs >= 0)) {
+        RefPtr<layers::Image> img = new SurfaceTextureImage(
+            mDecoder->mSurfaceHandle, inputInfo.mImageSize,
+            false /* NOT continuous */, gl::OriginPos::BottomLeft);
+
+        RefPtr<VideoData> v = VideoData::CreateFromImage(
+            inputInfo.mDisplaySize, offset,
+            TimeUnit::FromMicroseconds(presentationTimeUs),
+            TimeUnit::FromMicroseconds(inputInfo.mDurationUs), img,
+            !!(flags & MediaCodec::BUFFER_FLAG_SYNC_FRAME),
+            TimeUnit::FromMicroseconds(presentationTimeUs));
+
+        v->SetListener(std::move(releaseSample));
+        mDecoder->UpdateOutputStatus(std::move(v));
+      }
+
+      if (isEOS) {
+        mDecoder->DrainComplete();
+      }
     }
 
     void HandleError(const MediaResult& aError) override {
       mDecoder->Error(aError);
     }
 
     friend class RemoteDataDecoder;
 
@@ -146,22 +187,24 @@ class RemoteVideoDecoder : public Remote
     mIsCodecSupportAdaptivePlayback =
         mJavaDecoder->IsAdaptivePlaybackSupported();
     mIsHardwareAccelerated = mJavaDecoder->IsHardwareAccelerated();
     return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
   }
 
   RefPtr<MediaDataDecoder::FlushPromise> Flush() override {
     RefPtr<RemoteVideoDecoder> self = this;
-    return InvokeAsync(mTaskQueue, __func__, [self, this]() {
-      mInputInfos.Clear();
-      mSeekTarget.reset();
-      mLatestOutputTime.reset();
-      return RemoteDataDecoder::ProcessFlush();
-    });
+    return RemoteDataDecoder::Flush()->Then(
+        mTaskQueue, __func__,
+        [self](const FlushPromise::ResolveOrRejectValue& aValue) {
+          self->mInputInfos.Clear();
+          self->mSeekTarget.reset();
+          self->mLatestOutputTime.reset();
+          return FlushPromise::CreateAndResolveOrReject(aValue, __func__);
+        });
   }
 
   RefPtr<MediaDataDecoder::DecodePromise> Decode(
       MediaRawData* aSample) override {
     const VideoInfo* config =
         aSample->mTrackInfo ? aSample->mTrackInfo->GetAsVideoInfo() : &mConfig;
     MOZ_ASSERT(config);
 
@@ -173,23 +216,18 @@ class RemoteVideoDecoder : public Remote
 
   bool SupportDecoderRecycling() const override {
     return mIsCodecSupportAdaptivePlayback;
   }
 
   void SetSeekThreshold(const TimeUnit& aTime) override {
     RefPtr<RemoteVideoDecoder> self = this;
     nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
-        "RemoteVideoDecoder::SetSeekThreshold", [self, aTime]() {
-          if (aTime.IsValid()) {
-            self->mSeekTarget = Some(aTime);
-          } else {
-            self->mSeekTarget.reset();
-          }
-        });
+        "RemoteVideoDecoder::SetSeekThreshold",
+        [self, aTime]() { self->mSeekTarget = Some(aTime); });
     nsresult rv = mTaskQueue->Dispatch(runnable.forget());
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
   }
 
   bool IsUsefulData(const RefPtr<MediaData>& aSample) override {
     AssertOnTaskQueue();
 
@@ -211,105 +249,38 @@ class RemoteVideoDecoder : public Remote
     return mIsHardwareAccelerated;
   }
 
   ConversionRequired NeedsConversion() const override {
     return ConversionRequired::kNeedAnnexB;
   }
 
  private:
-  // Param and LocalRef are only valid for the duration of a JNI method call.
-  // Use GlobalRef as the parameter type to keep the Java object referenced
-  // until running.
-  void ProcessOutput(Sample::GlobalRef&& aSample) {
-    if (!mTaskQueue->IsCurrentThreadIn()) {
-      nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<Sample::GlobalRef&&>(
-          "RemoteVideoDecoder::ProcessOutput", this,
-          &RemoteVideoDecoder::ProcessOutput, std::move(aSample)));
-      MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-      Unused << rv;
-      return;
-    }
-
-    AssertOnTaskQueue();
-
-    UniquePtr<VideoData::Listener> releaseSample(
-        new CompositeListener(mJavaDecoder, aSample));
-
-    BufferInfo::LocalRef info = aSample->Info();
-    MOZ_ASSERT(info);
-
-    int32_t flags;
-    bool ok = NS_SUCCEEDED(info->Flags(&flags));
-
-    int32_t offset;
-    ok &= NS_SUCCEEDED(info->Offset(&offset));
-
-    int32_t size;
-    ok &= NS_SUCCEEDED(info->Size(&size));
-
-    int64_t presentationTimeUs;
-    ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs));
-
-    if (!ok) {
-      Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
-                        RESULT_DETAIL("VideoCallBack::HandleOutput")));
-      return;
-    }
-
-    InputInfo inputInfo;
-    ok = mInputInfos.Find(presentationTimeUs, inputInfo);
-    bool isEOS = !!(flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM);
-    if (!ok && !isEOS) {
-      // Ignore output with no corresponding input.
-      return;
-    }
-
-    if (ok && (size > 0 || presentationTimeUs >= 0)) {
-      RefPtr<layers::Image> img = new SurfaceTextureImage(
-          mSurfaceHandle, inputInfo.mImageSize, false /* NOT continuous */,
-          gl::OriginPos::BottomLeft);
-
-      RefPtr<VideoData> v = VideoData::CreateFromImage(
-          inputInfo.mDisplaySize, offset,
-          TimeUnit::FromMicroseconds(presentationTimeUs),
-          TimeUnit::FromMicroseconds(inputInfo.mDurationUs), img,
-          !!(flags & MediaCodec::BUFFER_FLAG_SYNC_FRAME),
-          TimeUnit::FromMicroseconds(presentationTimeUs));
-
-      v->SetListener(std::move(releaseSample));
-      RemoteDataDecoder::UpdateOutputStatus(std::move(v));
-    }
-
-    if (isEOS) {
-      DrainComplete();
-    }
-  }
-
   const VideoInfo mConfig;
   GeckoSurface::GlobalRef mSurface;
   AndroidSurfaceTextureHandle mSurfaceHandle;
   // Only accessed on reader's task queue.
   bool mIsCodecSupportAdaptivePlayback = false;
   // Can be accessed on any thread, but only written on during init.
   bool mIsHardwareAccelerated = false;
-  // Accessed on mTaskQueue and reader's TaskQueue. SimpleMap however is
-  // thread-safe, so it's okay to do so.
+  // Accessed on mTaskQueue, reader's TaskQueue and Java callback tread.
+  // SimpleMap however is thread-safe, so it's okay to do so.
   SimpleMap<InputInfo> mInputInfos;
   // Only accessed on the TaskQueue.
   Maybe<TimeUnit> mSeekTarget;
   Maybe<TimeUnit> mLatestOutputTime;
 };
 
 class RemoteAudioDecoder : public RemoteDataDecoder {
  public:
   RemoteAudioDecoder(const AudioInfo& aConfig, MediaFormat::Param aFormat,
                      const nsString& aDrmStubId, TaskQueue* aTaskQueue)
       : RemoteDataDecoder(MediaData::Type::AUDIO_DATA, aConfig.mMimeType,
-                          aFormat, aDrmStubId, aTaskQueue) {
+                          aFormat, aDrmStubId, aTaskQueue),
+        mConfig(aConfig) {
     JNIEnv* const env = jni::GetEnvForThread();
 
     bool formatHasCSD = false;
     NS_ENSURE_SUCCESS_VOID(
         aFormat->ContainsKey(NS_LITERAL_STRING("csd-0"), &formatHasCSD));
 
     if (!formatHasCSD && aConfig.mCodecSpecificConfig->Length() >= 2) {
       jni::ByteBuffer::LocalRef buffer(env);
@@ -344,133 +315,92 @@ class RemoteAudioDecoder : public Remote
     explicit CallbacksSupport(RemoteAudioDecoder* aDecoder)
         : mDecoder(aDecoder) {}
 
     void HandleInput(int64_t aTimestamp, bool aProcessed) override {
       mDecoder->UpdateInputStatus(aTimestamp, aProcessed);
     }
 
     void HandleOutput(Sample::Param aSample) override {
-      // aSample will be implicitly converted into a GlobalRef.
-      mDecoder->ProcessOutput(std::move(aSample));
+      BufferInfo::LocalRef info = aSample->Info();
+
+      int32_t flags;
+      bool ok = NS_SUCCEEDED(info->Flags(&flags));
+
+      int32_t offset;
+      ok &= NS_SUCCEEDED(info->Offset(&offset));
+
+      int64_t presentationTimeUs;
+      ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs));
+
+      int32_t size;
+      ok &= NS_SUCCEEDED(info->Size(&size));
+
+      if (!ok) {
+        HandleError(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                RESULT_DETAIL("AudioCallBack::HandleOutput")));
+        return;
+      }
+
+      if (size > 0) {
+#ifdef MOZ_SAMPLE_TYPE_S16
+        const int32_t numSamples = size / 2;
+#else
+#error We only support 16-bit integer PCM
+#endif
+
+        const int32_t numFrames = numSamples / mOutputChannels;
+        AlignedAudioBuffer audio(numSamples);
+        if (!audio) {
+          mDecoder->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
+          return;
+        }
+
+        jni::ByteBuffer::LocalRef dest =
+            jni::ByteBuffer::New(audio.get(), size);
+        aSample->WriteToByteBuffer(dest);
+
+        RefPtr<AudioData> data = new AudioData(
+            0, TimeUnit::FromMicroseconds(presentationTimeUs),
+            FramesToTimeUnit(numFrames, mOutputSampleRate), numFrames,
+            std::move(audio), mOutputChannels, mOutputSampleRate);
+
+        mDecoder->UpdateOutputStatus(std::move(data));
+      }
+
+      if ((flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM) != 0) {
+        mDecoder->DrainComplete();
+      }
     }
 
     void HandleOutputFormatChanged(MediaFormat::Param aFormat) override {
-      int32_t outputChannels = 0;
-      aFormat->GetInteger(NS_LITERAL_STRING("channel-count"), &outputChannels);
-      AudioConfig::ChannelLayout layout(outputChannels);
+      aFormat->GetInteger(NS_LITERAL_STRING("channel-count"), &mOutputChannels);
+      AudioConfig::ChannelLayout layout(mOutputChannels);
       if (!layout.IsValid()) {
         mDecoder->Error(MediaResult(
             NS_ERROR_DOM_MEDIA_FATAL_ERR,
-            RESULT_DETAIL("Invalid channel layout:%d", outputChannels)));
+            RESULT_DETAIL("Invalid channel layout:%d", mOutputChannels)));
         return;
       }
-
-      int32_t sampleRate = 0;
-      aFormat->GetInteger(NS_LITERAL_STRING("sample-rate"), &sampleRate);
+      aFormat->GetInteger(NS_LITERAL_STRING("sample-rate"), &mOutputSampleRate);
       LOG("Audio output format changed: channels:%d sample rate:%d",
-          outputChannels, sampleRate);
-
-      mDecoder->ProcessOutputFormatChange(outputChannels, sampleRate);
+          mOutputChannels, mOutputSampleRate);
     }
 
     void HandleError(const MediaResult& aError) override {
       mDecoder->Error(aError);
     }
 
    private:
     RemoteAudioDecoder* mDecoder;
+    int32_t mOutputChannels;
+    int32_t mOutputSampleRate;
   };
 
-  // Param and LocalRef are only valid for the duration of a JNI method call.
-  // Use GlobalRef as the parameter type to keep the Java object referenced
-  // until running.
-  void ProcessOutput(Sample::GlobalRef&& aSample) {
-    if (!mTaskQueue->IsCurrentThreadIn()) {
-      nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<Sample::GlobalRef&&>(
-          "RemoteAudioDecoder::ProcessOutput", this,
-          &RemoteAudioDecoder::ProcessOutput, std::move(aSample)));
-      MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-      Unused << rv;
-      return;
-    }
-
-    AssertOnTaskQueue();
-
-    RenderOrReleaseOutput autoRelease(mJavaDecoder, aSample);
-
-    BufferInfo::LocalRef info = aSample->Info();
-    MOZ_ASSERT(info);
-
-    int32_t flags;
-    bool ok = NS_SUCCEEDED(info->Flags(&flags));
-
-    int32_t offset;
-    ok &= NS_SUCCEEDED(info->Offset(&offset));
-
-    int64_t presentationTimeUs;
-    ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs));
-
-    int32_t size;
-    ok &= NS_SUCCEEDED(info->Size(&size));
-
-    if (!ok) {
-      Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
-      return;
-    }
-
-    if (size > 0) {
-#ifdef MOZ_SAMPLE_TYPE_S16
-      const int32_t numSamples = size / 2;
-#else
-#error We only support 16-bit integer PCM
-#endif
-
-      const int32_t numFrames = numSamples / mOutputChannels;
-      AlignedAudioBuffer audio(numSamples);
-      if (!audio) {
-        Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
-        return;
-      }
-
-      jni::ByteBuffer::LocalRef dest = jni::ByteBuffer::New(audio.get(), size);
-      aSample->WriteToByteBuffer(dest);
-
-      RefPtr<AudioData> data = new AudioData(
-          0, TimeUnit::FromMicroseconds(presentationTimeUs),
-          FramesToTimeUnit(numFrames, mOutputSampleRate), numFrames,
-          std::move(audio), mOutputChannels, mOutputSampleRate);
-
-      UpdateOutputStatus(std::move(data));
-    }
-
-    if ((flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM) != 0) {
-      DrainComplete();
-    }
-  }
-
-  void ProcessOutputFormatChange(int32_t aChannels, int32_t aSampleRate) {
-    if (!mTaskQueue->IsCurrentThreadIn()) {
-      nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<int32_t, int32_t>(
-          "RemoteAudioDecoder::ProcessOutputFormatChange", this,
-          &RemoteAudioDecoder::ProcessOutputFormatChange, aChannels,
-          aSampleRate));
-      MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-      Unused << rv;
-      return;
-    }
-
-    AssertOnTaskQueue();
-
-    mOutputChannels = aChannels;
-    mOutputSampleRate = aSampleRate;
-  }
-
-  int32_t mOutputChannels;
-  int32_t mOutputSampleRate;
+  const AudioInfo mConfig;
 };
 
 already_AddRefed<MediaDataDecoder> RemoteDataDecoder::CreateAudioDecoder(
     const CreateDecoderParams& aParams, const nsString& aDrmStubId,
     CDMProxy* aProxy) {
   const AudioInfo& config = aParams.AudioConfig();
   MediaFormat::LocalRef format;
   NS_ENSURE_SUCCESS(
@@ -513,74 +443,69 @@ RemoteDataDecoder::RemoteDataDecoder(Med
       mMimeType(aMimeType),
       mFormat(aFormat),
       mDrmStubId(aDrmStubId),
       mTaskQueue(aTaskQueue),
       mNumPendingInputs(0) {}
 
 RefPtr<MediaDataDecoder::FlushPromise> RemoteDataDecoder::Flush() {
   RefPtr<RemoteDataDecoder> self = this;
-  return InvokeAsync(mTaskQueue, this, __func__,
-                     &RemoteDataDecoder::ProcessFlush);
-}
-
-RefPtr<MediaDataDecoder::FlushPromise> RemoteDataDecoder::ProcessFlush() {
-  AssertOnTaskQueue();
-
-  mDecodedData = DecodedData();
-  UpdatePendingInputStatus(PendingOp::CLEAR);
-  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-  mDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-  SetState(State::DRAINED);
-  mJavaDecoder->Flush();
-  return FlushPromise::CreateAndResolve(true, __func__);
+  return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+    mDecodedData = DecodedData();
+    mNumPendingInputs = 0;
+    mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+    mDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+    mDrainStatus = DrainStatus::DRAINED;
+    mJavaDecoder->Flush();
+    return FlushPromise::CreateAndResolve(true, __func__);
+  });
 }
 
 RefPtr<MediaDataDecoder::DecodePromise> RemoteDataDecoder::Drain() {
   RefPtr<RemoteDataDecoder> self = this;
   return InvokeAsync(mTaskQueue, __func__, [self, this]() {
-    if (GetState() == State::SHUTDOWN) {
+    if (mShutdown) {
       return DecodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED,
                                             __func__);
     }
     RefPtr<DecodePromise> p = mDrainPromise.Ensure(__func__);
-    if (GetState() == State::DRAINED) {
+    if (mDrainStatus == DrainStatus::DRAINED) {
       // There's no operation to perform other than returning any already
       // decoded data.
       ReturnDecodedData();
       return p;
     }
 
-    if (GetState() == State::DRAINING) {
+    if (mDrainStatus == DrainStatus::DRAINING) {
       // Draining operation already pending, let it complete its course.
       return p;
     }
 
     BufferInfo::LocalRef bufferInfo;
     nsresult rv = BufferInfo::New(&bufferInfo);
     if (NS_FAILED(rv)) {
       return DecodePromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
     }
-    SetState(State::DRAINING);
+    mDrainStatus = DrainStatus::DRAINING;
     bufferInfo->Set(0, 0, -1, MediaCodec::BUFFER_FLAG_END_OF_STREAM);
     mJavaDecoder->Input(nullptr, bufferInfo, nullptr);
     return p;
   });
 }
 
 RefPtr<ShutdownPromise> RemoteDataDecoder::Shutdown() {
   LOG("");
   RefPtr<RemoteDataDecoder> self = this;
   return InvokeAsync(mTaskQueue, this, __func__,
                      &RemoteDataDecoder::ProcessShutdown);
 }
 
 RefPtr<ShutdownPromise> RemoteDataDecoder::ProcessShutdown() {
   AssertOnTaskQueue();
-  SetState(State::SHUTDOWN);
+  mShutdown = true;
   if (mJavaDecoder) {
     mJavaDecoder->Release();
     mJavaDecoder = nullptr;
   }
 
   if (mJavaCallbacks) {
     JavaCallbacksSupport::GetNative(mJavaCallbacks)->Cancel();
     JavaCallbacksSupport::DisposeNative(mJavaCallbacks);
@@ -663,120 +588,116 @@ RefPtr<MediaDataDecoder::DecodePromise> 
     BufferInfo::LocalRef bufferInfo;
     nsresult rv = BufferInfo::New(&bufferInfo);
     if (NS_FAILED(rv)) {
       return DecodePromise::CreateAndReject(
           MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
     }
     bufferInfo->Set(0, sample->Size(), sample->mTime.ToMicroseconds(), 0);
 
-    self->SetState(State::DRAINABLE);
+    self->mDrainStatus = DrainStatus::DRAINABLE;
     return self->mJavaDecoder->Input(bytes, bufferInfo,
                                      GetCryptoInfoFromSample(sample))
                ? self->mDecodePromise.Ensure(__func__)
                : DecodePromise::CreateAndReject(
                      MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
   });
 }
 
-void RemoteDataDecoder::UpdatePendingInputStatus(PendingOp aOp) {
-  AssertOnTaskQueue();
-  switch (aOp) {
-    case PendingOp::INCREASE:
-      mNumPendingInputs++;
-      break;
-    case PendingOp::DECREASE:
-      mNumPendingInputs--;
-      break;
-    case PendingOp::CLEAR:
-      mNumPendingInputs = 0;
-      break;
-  }
-}
-
 void RemoteDataDecoder::UpdateInputStatus(int64_t aTimestamp, bool aProcessed) {
   if (!mTaskQueue->IsCurrentThreadIn()) {
     nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<int64_t, bool>(
         "RemoteDataDecoder::UpdateInputStatus", this,
         &RemoteDataDecoder::UpdateInputStatus, aTimestamp, aProcessed));
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
     return;
   }
   AssertOnTaskQueue();
-  if (GetState() == State::SHUTDOWN) {
+  if (mShutdown) {
     return;
   }
 
   if (!aProcessed) {
-    UpdatePendingInputStatus(PendingOp::INCREASE);
-  } else if (HasPendingInputs()) {
-    UpdatePendingInputStatus(PendingOp::DECREASE);
+    mNumPendingInputs++;
+  } else if (mNumPendingInputs > 0) {
+    mNumPendingInputs--;
   }
 
-  if (!HasPendingInputs() ||  // Input has been processed, request the next one.
+  if (mNumPendingInputs ==
+          0 ||  // Input has been processed, request the next one.
       !mDecodedData.IsEmpty()) {  // Previous output arrived before Decode().
     ReturnDecodedData();
   }
 }
 
 void RemoteDataDecoder::UpdateOutputStatus(RefPtr<MediaData>&& aSample) {
+  if (!mTaskQueue->IsCurrentThreadIn()) {
+    nsresult rv =
+        mTaskQueue->Dispatch(NewRunnableMethod<const RefPtr<MediaData>>(
+            "RemoteDataDecoder::UpdateOutputStatus", this,
+            &RemoteDataDecoder::UpdateOutputStatus, std::move(aSample)));
+    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+    Unused << rv;
+    return;
+  }
   AssertOnTaskQueue();
-  if (GetState() == State::SHUTDOWN) {
+  if (mShutdown) {
     return;
   }
   if (IsUsefulData(aSample)) {
     mDecodedData.AppendElement(std::move(aSample));
   }
   ReturnDecodedData();
 }
 
 void RemoteDataDecoder::ReturnDecodedData() {
   AssertOnTaskQueue();
-  MOZ_ASSERT(GetState() != State::SHUTDOWN);
+  MOZ_ASSERT(!mShutdown);
 
   // We only want to clear mDecodedData when we have resolved the promises.
   if (!mDecodePromise.IsEmpty()) {
     mDecodePromise.Resolve(std::move(mDecodedData), __func__);
     mDecodedData = DecodedData();
   } else if (!mDrainPromise.IsEmpty() &&
-             (!mDecodedData.IsEmpty() || GetState() == State::DRAINED)) {
+             (!mDecodedData.IsEmpty() ||
+              mDrainStatus == DrainStatus::DRAINED)) {
     mDrainPromise.Resolve(std::move(mDecodedData), __func__);
     mDecodedData = DecodedData();
   }
 }
 
 void RemoteDataDecoder::DrainComplete() {
   if (!mTaskQueue->IsCurrentThreadIn()) {
     nsresult rv = mTaskQueue->Dispatch(
         NewRunnableMethod("RemoteDataDecoder::DrainComplete", this,
                           &RemoteDataDecoder::DrainComplete));
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
     return;
   }
   AssertOnTaskQueue();
-  if (GetState() == State::SHUTDOWN) {
+  if (mShutdown) {
     return;
   }
-  SetState(State::DRAINED);
+  mDrainStatus = DrainStatus::DRAINED;
   ReturnDecodedData();
   // Make decoder accept input again.
   mJavaDecoder->Flush();
 }
 
 void RemoteDataDecoder::Error(const MediaResult& aError) {
   if (!mTaskQueue->IsCurrentThreadIn()) {
     nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<MediaResult>(
         "RemoteDataDecoder::Error", this, &RemoteDataDecoder::Error, aError));
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
     return;
   }
   AssertOnTaskQueue();
-  if (GetState() == State::SHUTDOWN) {
+  if (mShutdown) {
     return;
   }
   mDecodePromise.RejectIfExists(aError, __func__);
   mDrainPromise.RejectIfExists(aError, __func__);
 }
 
 }  // namespace mozilla
--- a/dom/media/platforms/android/RemoteDataDecoder.h
+++ b/dom/media/platforms/android/RemoteDataDecoder.h
@@ -37,60 +37,46 @@ class RemoteDataDecoder : public MediaDa
 
  protected:
   virtual ~RemoteDataDecoder() {}
   RemoteDataDecoder(MediaData::Type aType, const nsACString& aMimeType,
                     java::sdk::MediaFormat::Param aFormat,
                     const nsString& aDrmStubId, TaskQueue* aTaskQueue);
 
   // Methods only called on mTaskQueue.
-  RefPtr<FlushPromise> ProcessFlush();
   RefPtr<ShutdownPromise> ProcessShutdown();
   void UpdateInputStatus(int64_t aTimestamp, bool aProcessed);
   void UpdateOutputStatus(RefPtr<MediaData>&& aSample);
   void ReturnDecodedData();
   void DrainComplete();
   void Error(const MediaResult& aError);
   void AssertOnTaskQueue() { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); }
 
-  enum class State { DRAINED, DRAINABLE, DRAINING, SHUTDOWN };
-  void SetState(State aState) {
-    AssertOnTaskQueue();
-    mState = aState;
-  }
-  State GetState() {
-    AssertOnTaskQueue();
-    return mState;
-  }
-
   // Whether the sample will be used.
   virtual bool IsUsefulData(const RefPtr<MediaData>& aSample) { return true; }
 
   MediaData::Type mType;
 
   nsAutoCString mMimeType;
   java::sdk::MediaFormat::GlobalRef mFormat;
 
   java::CodecProxy::GlobalRef mJavaDecoder;
   java::CodecProxy::NativeCallbacks::GlobalRef mJavaCallbacks;
   nsString mDrmStubId;
 
   RefPtr<TaskQueue> mTaskQueue;
-
- private:
-  enum class PendingOp { INCREASE, DECREASE, CLEAR };
-  void UpdatePendingInputStatus(PendingOp aOp);
-  size_t HasPendingInputs() {
-    AssertOnTaskQueue();
-    return mNumPendingInputs > 0;
-  }
-
-  // The following members must only be accessed on mTaskqueue.
+  // Only ever accessed on mTaskqueue.
+  bool mShutdown = false;
   MozPromiseHolder<DecodePromise> mDecodePromise;
   MozPromiseHolder<DecodePromise> mDrainPromise;
+  enum class DrainStatus {
+    DRAINED,
+    DRAINABLE,
+    DRAINING,
+  };
+  DrainStatus mDrainStatus = DrainStatus::DRAINED;
   DecodedData mDecodedData;
-  State mState = State::DRAINED;
   size_t mNumPendingInputs;
 };
 
 }  // namespace mozilla
 
 #endif
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -241,21 +241,17 @@ RefPtr<MediaDataDecoder::DecodePromise> 
 AppleVTDecoder::AppleFrameRef* AppleVTDecoder::CreateAppleFrameRef(
     const MediaRawData* aSample) {
   MOZ_ASSERT(aSample);
   return new AppleFrameRef(*aSample);
 }
 
 void AppleVTDecoder::SetSeekThreshold(const media::TimeUnit& aTime) {
   LOG("SetSeekThreshold %lld", aTime.ToMicroseconds());
-  if (aTime.IsValid()) {
-    mSeekTargetThreshold = Some(aTime);
-  } else {
-    mSeekTargetThreshold.reset();
-  }
+  mSeekTargetThreshold = Some(aTime);
 }
 
 //
 // Implementation details.
 //
 
 // Callback passed to the VideoToolbox decoder for returning data.
 // This needs to be static because the API takes a C-style pair of
--- a/dom/media/platforms/wmf/WMFMediaDataDecoder.h
+++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.h
@@ -53,21 +53,17 @@ class MFTManager {
     return false;
   }
 
   virtual TrackInfo::TrackType GetType() = 0;
 
   virtual nsCString GetDescriptionName() const = 0;
 
   virtual void SetSeekThreshold(const media::TimeUnit& aTime) {
-    if (aTime.IsValid()) {
-      mSeekTargetThreshold = Some(aTime);
-    } else {
-      mSeekTargetThreshold.reset();
-    }
+    mSeekTargetThreshold = Some(aTime);
   }
 
   virtual MediaDataDecoder::ConversionRequired NeedsConversion() const {
     return MediaDataDecoder::ConversionRequired::kNeedNone;
   }
 
  protected:
   // IMFTransform wrapper that performs the decoding.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
@@ -89,18 +89,27 @@ public final class CodecProxy {
         public synchronized void onOutput(Sample sample) throws RemoteException {
             if (mCodecProxyReleased) {
                 sample.dispose();
                 return;
             }
             if (mOutputSurface != null) {
                 // Don't render to surface just yet. Callback will make that happen when it's time.
                 mSurfaceOutputs.offer(sample);
+                mCallbacks.onOutput(sample);
+            } else {
+                // Non-surface output needs no rendering.
+                try {
+                    mCallbacks.onOutput(sample);
+                    mRemote.releaseOutput(sample, false);
+                    sample.dispose();
+                } catch (Exception e) {
+                    reportError(true);
+                }
             }
-            mCallbacks.onOutput(sample);
         }
 
         @Override
         public void onError(boolean fatal) throws RemoteException {
             reportError(fatal);
         }
 
         private synchronized void reportError(boolean fatal) {