Bug 1206568: P1. Ensure FFmpeg decoder is only accessed through the decoder's task queue. r=cpearce
authorJean-Yves Avenard <jyavenard@mozilla.com>
Mon, 21 Sep 2015 13:54:29 +1000
changeset 607013 0123f6fe9832325693b692f1b9e1ede0fbf5f63e
parent 607012 aa99936362c2dbef1886f67d32641199bae838ec
child 607014 10db198e218b83f0637a2ba59667d22ad2521119
push id93006
push userjyavenard@mozilla.com
push dateThu, 15 Oct 2015 05:15:33 +0000
treeherdertry@45ea2a01301e [default view] [failures only]
reviewerscpearce
bugs1206568
milestone43.0a2
Bug 1206568: P1. Ensure FFmpeg decoder is only accessed through the decoder's task queue. r=cpearce Add strong assertion that it is indeed the case.
dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h
dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
dom/media/platforms/ffmpeg/FFmpegDataDecoder.h
dom/media/platforms/ffmpeg/FFmpegH264Decoder.cpp
dom/media/platforms/ffmpeg/FFmpegH264Decoder.h
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
@@ -13,18 +13,17 @@
 #define MAX_CHANNELS 16
 
 namespace mozilla
 {
 
 FFmpegAudioDecoder<LIBAV_VER>::FFmpegAudioDecoder(
   FlushableTaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback,
   const AudioInfo& aConfig)
-  : FFmpegDataDecoder(aTaskQueue, GetCodecId(aConfig.mMimeType))
-  , mCallback(aCallback)
+  : FFmpegDataDecoder(aTaskQueue, aCallback, GetCodecId(aConfig.mMimeType))
 {
   MOZ_COUNT_CTOR(FFmpegAudioDecoder);
   // Use a new MediaByteBuffer as the object will be modified during initialization.
   mExtraData = new MediaByteBuffer;
   mExtraData->AppendElements(*aConfig.mCodecSpecificConfig);
 }
 
 nsRefPtr<MediaDataDecoder::InitPromise>
@@ -80,16 +79,17 @@ CopyAndPackAudio(AVFrame* aFrame, uint32
   }
 
   return audio.forget();
 }
 
 void
 FFmpegAudioDecoder<LIBAV_VER>::DecodePacket(MediaRawData* aSample)
 {
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   AVPacket packet;
   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.");
@@ -155,22 +155,22 @@ nsresult
 FFmpegAudioDecoder<LIBAV_VER>::Input(MediaRawData* aSample)
 {
   nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableMethodWithArg<nsRefPtr<MediaRawData>>(
     this, &FFmpegAudioDecoder::DecodePacket, nsRefPtr<MediaRawData>(aSample)));
   mTaskQueue->Dispatch(runnable.forget());
   return NS_OK;
 }
 
-nsresult
-FFmpegAudioDecoder<LIBAV_VER>::Drain()
+void
+FFmpegAudioDecoder<LIBAV_VER>::ProcessDrain()
 {
-  mTaskQueue->AwaitIdle();
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+  ProcessFlush();
   mCallback->DrainComplete();
-  return Flush();
 }
 
 AVCodecID
 FFmpegAudioDecoder<LIBAV_VER>::GetCodecId(const nsACString& aMimeType)
 {
   if (aMimeType.EqualsLiteral("audio/mpeg")) {
     return AV_CODEC_ID_MP3;
   }
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h
@@ -22,20 +22,18 @@ class FFmpegAudioDecoder<LIBAV_VER> : pu
 public:
   FFmpegAudioDecoder(FlushableTaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      const AudioInfo& aConfig);
   virtual ~FFmpegAudioDecoder();
 
   virtual nsRefPtr<InitPromise> Init() override;
   virtual nsresult Input(MediaRawData* aSample) override;
-  virtual nsresult Drain() override;
+  virtual void ProcessDrain() override;
   static AVCodecID GetCodecId(const nsACString& aMimeType);
 
 private:
   void DecodePacket(MediaRawData* aSample);
-
-  MediaDataDecoderCallback* mCallback;
 };
 
 } // namespace mozilla
 
 #endif // __FFmpegAACDecoder_h__
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
@@ -16,22 +16,26 @@
 
 namespace mozilla
 {
 
 bool FFmpegDataDecoder<LIBAV_VER>::sFFmpegInitDone = false;
 StaticMutex FFmpegDataDecoder<LIBAV_VER>::sMonitor;
 
 FFmpegDataDecoder<LIBAV_VER>::FFmpegDataDecoder(FlushableTaskQueue* aTaskQueue,
+                                                MediaDataDecoderCallback* aCallback,
                                                 AVCodecID aCodecID)
   : mTaskQueue(aTaskQueue)
+  , mCallback(aCallback)
   , mCodecContext(nullptr)
   , mFrame(NULL)
   , mExtraData(nullptr)
   , mCodecID(aCodecID)
+  , mMonitor("FFMpegaDataDecoder")
+  , mIsFlushing(false)
 {
   MOZ_COUNT_CTOR(FFmpegDataDecoder);
 }
 
 FFmpegDataDecoder<LIBAV_VER>::~FFmpegDataDecoder()
 {
   MOZ_COUNT_DTOR(FFmpegDataDecoder);
 }
@@ -123,46 +127,89 @@ FFmpegDataDecoder<LIBAV_VER>::InitDecode
     return NS_ERROR_FAILURE;
   }
 
   FFMPEG_LOG("FFmpeg init successful.");
   return NS_OK;
 }
 
 nsresult
-FFmpegDataDecoder<LIBAV_VER>::Flush()
+FFmpegDataDecoder<LIBAV_VER>::Shutdown()
 {
-  mTaskQueue->Flush();
-  avcodec_flush_buffers(mCodecContext);
+  if (mTaskQueue) {
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableMethod(this, &FFmpegDataDecoder<LIBAV_VER>::ProcessShutdown);
+    mTaskQueue->Dispatch(runnable.forget());
+  } else {
+    ProcessShutdown();
+  }
   return NS_OK;
 }
 
 nsresult
-FFmpegDataDecoder<LIBAV_VER>::Shutdown()
+FFmpegDataDecoder<LIBAV_VER>::Flush()
+{
+  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+  mIsFlushing = true;
+  mTaskQueue->Flush();
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableMethod(this, &FFmpegDataDecoder<LIBAV_VER>::ProcessFlush);
+  MonitorAutoLock mon(mMonitor);
+  mTaskQueue->Dispatch(runnable.forget());
+  while (mIsFlushing) {
+    mon.Wait();
+  }
+  return NS_OK;
+}
+
+nsresult
+FFmpegDataDecoder<LIBAV_VER>::Drain()
+{
+  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableMethod(this, &FFmpegDataDecoder<LIBAV_VER>::ProcessDrain);
+  mTaskQueue->Dispatch(runnable.forget());
+  return NS_OK;
+}
+
+void
+FFmpegDataDecoder<LIBAV_VER>::ProcessFlush()
+{
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+  if (mCodecContext) {
+    avcodec_flush_buffers(mCodecContext);
+  }
+  MonitorAutoLock mon(mMonitor);
+  mIsFlushing = false;
+  mon.NotifyAll();
+}
+
+void
+FFmpegDataDecoder<LIBAV_VER>::ProcessShutdown()
 {
   StaticMutexAutoLock mon(sMonitor);
 
   if (sFFmpegInitDone) {
     avcodec_close(mCodecContext);
     av_freep(&mCodecContext);
 #if LIBAVCODEC_VERSION_MAJOR >= 55
     av_frame_free(&mFrame);
 #elif LIBAVCODEC_VERSION_MAJOR == 54
     avcodec_free_frame(&mFrame);
 #else
     delete mFrame;
     mFrame = nullptr;
 #endif
   }
-  return NS_OK;
 }
 
 AVFrame*
 FFmpegDataDecoder<LIBAV_VER>::PrepareFrame()
 {
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
 #if LIBAVCODEC_VERSION_MAJOR >= 55
   if (mFrame) {
     av_frame_unref(mFrame);
   } else {
     mFrame = av_frame_alloc();
   }
 #elif LIBAVCODEC_VERSION_MAJOR == 54
   if (mFrame) {
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h
@@ -18,37 +18,53 @@ template <int V>
 class FFmpegDataDecoder : public MediaDataDecoder
 {
 };
 
 template <>
 class FFmpegDataDecoder<LIBAV_VER> : public MediaDataDecoder
 {
 public:
-  FFmpegDataDecoder(FlushableTaskQueue* aTaskQueue, AVCodecID aCodecID);
+  FFmpegDataDecoder(FlushableTaskQueue* aTaskQueue,
+                    MediaDataDecoderCallback* aCallback,
+                    AVCodecID aCodecID);
   virtual ~FFmpegDataDecoder();
 
   static bool Link();
 
   virtual nsRefPtr<InitPromise> Init() override = 0;
   virtual nsresult Input(MediaRawData* aSample) override = 0;
   virtual nsresult Flush() override;
-  virtual nsresult Drain() override = 0;
+  virtual nsresult Drain() override;
   virtual nsresult Shutdown() override;
 
 protected:
+  // Flush and Drain operation, always run
+  virtual void ProcessFlush();
+  virtual void ProcessDrain() = 0;
+  virtual void ProcessShutdown();
   AVFrame*        PrepareFrame();
   nsresult        InitDecoder();
 
-  FlushableTaskQueue* mTaskQueue;
+  nsRefPtr<FlushableTaskQueue> mTaskQueue;
+  MediaDataDecoderCallback* mCallback;
+
   AVCodecContext* mCodecContext;
   AVFrame*        mFrame;
   nsRefPtr<MediaByteBuffer> mExtraData;
   AVCodecID mCodecID;
 
+  // For wait on mIsFlushing during Shutdown() process.
+  // Protects mReorderQueue.
+  Monitor mMonitor;
+  // Set on reader/decode thread calling Flush() to indicate that output is
+  // not required and so input samples on mTaskQueue need not be processed.
+  // Cleared on mTaskQueue in ProcessDrain().
+  Atomic<bool> mIsFlushing;
+
 private:
   static bool sFFmpegInitDone;
   static StaticMutex sMonitor;
 };
 
 } // namespace mozilla
 
 #endif // __FFmpegDataDecoder_h__
--- a/dom/media/platforms/ffmpeg/FFmpegH264Decoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegH264Decoder.cpp
@@ -23,18 +23,17 @@ typedef mozilla::layers::PlanarYCbCrImag
 
 namespace mozilla
 {
 
 FFmpegH264Decoder<LIBAV_VER>::FFmpegH264Decoder(
   FlushableTaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback,
   const VideoInfo& aConfig,
   ImageContainer* aImageContainer)
-  : FFmpegDataDecoder(aTaskQueue, GetCodecId(aConfig.mMimeType))
-  , mCallback(aCallback)
+  : FFmpegDataDecoder(aTaskQueue, aCallback, GetCodecId(aConfig.mMimeType))
   , mImageContainer(aImageContainer)
   , mDisplayWidth(aConfig.mDisplay.width)
   , mDisplayHeight(aConfig.mDisplay.height)
   , mCodecParser(nullptr)
 {
   MOZ_COUNT_CTOR(FFmpegH264Decoder);
   // Use a new MediaByteBuffer as the object will be modified during initialization.
   mExtraData = new MediaByteBuffer;
@@ -52,30 +51,33 @@ FFmpegH264Decoder<LIBAV_VER>::Init()
   mCodecContext->release_buffer = ReleaseBufferCb;
 
   return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
 }
 
 int64_t
 FFmpegH264Decoder<LIBAV_VER>::GetPts(const AVPacket& packet)
 {
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
 #if LIBAVCODEC_VERSION_MAJOR == 53
   if (mFrame->pkt_pts == 0) {
     return mFrame->pkt_dts;
   } else {
     return mFrame->pkt_pts;
   }
 #else
   return mFrame->pkt_pts;
 #endif
 }
 
 FFmpegH264Decoder<LIBAV_VER>::DecodeResult
 FFmpegH264Decoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample)
 {
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
   uint8_t* inputData = const_cast<uint8_t*>(aSample->Data());
   size_t inputSize = aSample->Size();
 
   if (inputSize && (mCodecID == AV_CODEC_ID_VP8
 #if LIBAVCODEC_VERSION_MAJOR >= 55
       || mCodecID == AV_CODEC_ID_VP9
 #endif
       )) {
@@ -117,16 +119,18 @@ FFmpegH264Decoder<LIBAV_VER>::DoDecodeFr
   }
   return DoDecodeFrame(aSample, inputData, inputSize);
 }
 
 FFmpegH264Decoder<LIBAV_VER>::DecodeResult
 FFmpegH264Decoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample,
                                             uint8_t* aData, int aSize)
 {
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
   AVPacket packet;
   av_init_packet(&packet);
 
   packet.data = aData;
   packet.size = aSize;
   packet.dts = aSample->mTimecode;
   packet.pts = aSample->mTime;
   packet.flags = aSample->mKeyframe ? AV_PKT_FLAG_KEY : 0;
@@ -203,16 +207,18 @@ FFmpegH264Decoder<LIBAV_VER>::DoDecodeFr
     return DecodeResult::DECODE_FRAME;
   }
   return DecodeResult::DECODE_NO_FRAME;
 }
 
 void
 FFmpegH264Decoder<LIBAV_VER>::DecodeFrame(MediaRawData* aSample)
 {
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
   if (DoDecodeFrame(aSample) != DecodeResult::DECODE_ERROR &&
       mTaskQueue->IsEmpty()) {
     mCallback->InputExhausted();
   }
 }
 
 /* static */ int
 FFmpegH264Decoder<LIBAV_VER>::AllocateBufferCb(AVCodecContext* aCodecContext,
@@ -334,42 +340,25 @@ FFmpegH264Decoder<LIBAV_VER>::Input(Medi
       this, &FFmpegH264Decoder<LIBAV_VER>::DecodeFrame,
       nsRefPtr<MediaRawData>(aSample)));
   mTaskQueue->Dispatch(runnable.forget());
 
   return NS_OK;
 }
 
 void
-FFmpegH264Decoder<LIBAV_VER>::DoDrain()
+FFmpegH264Decoder<LIBAV_VER>::ProcessDrain()
 {
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   nsRefPtr<MediaRawData> empty(new MediaRawData());
   while (DoDecodeFrame(empty) == DecodeResult::DECODE_FRAME) {
   }
   mCallback->DrainComplete();
 }
 
-nsresult
-FFmpegH264Decoder<LIBAV_VER>::Drain()
-{
-  nsCOMPtr<nsIRunnable> runnable(
-    NS_NewRunnableMethod(this, &FFmpegH264Decoder<LIBAV_VER>::DoDrain));
-  mTaskQueue->Dispatch(runnable.forget());
-
-  return NS_OK;
-}
-
-nsresult
-FFmpegH264Decoder<LIBAV_VER>::Flush()
-{
-  nsresult rv = FFmpegDataDecoder::Flush();
-  // Even if the above fails we may as well clear our frame queue.
-  return rv;
-}
-
 FFmpegH264Decoder<LIBAV_VER>::~FFmpegH264Decoder()
 {
   MOZ_COUNT_DTOR(FFmpegH264Decoder);
   if (mCodecParser) {
     av_parser_close(mCodecParser);
     mCodecParser = nullptr;
   }
 }
--- a/dom/media/platforms/ffmpeg/FFmpegH264Decoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegH264Decoder.h
@@ -33,18 +33,17 @@ public:
   FFmpegH264Decoder(FlushableTaskQueue* aTaskQueue,
                     MediaDataDecoderCallback* aCallback,
                     const VideoInfo& aConfig,
                     ImageContainer* aImageContainer);
   virtual ~FFmpegH264Decoder();
 
   virtual nsRefPtr<InitPromise> Init() override;
   virtual nsresult Input(MediaRawData* aSample) override;
-  virtual nsresult Drain() override;
-  virtual nsresult Flush() override;
+  virtual void ProcessDrain() override;
   static AVCodecID GetCodecId(const nsACString& aMimeType);
 
 private:
   void DecodeFrame(MediaRawData* aSample);
   DecodeResult DoDecodeFrame(MediaRawData* aSample);
   DecodeResult DoDecodeFrame(MediaRawData* aSample, uint8_t* aData, int aSize);
   void DoDrain();
   void OutputDelayedFrames();
@@ -57,17 +56,16 @@ private:
    */
   int AllocateYUV420PVideoBuffer(AVCodecContext* aCodecContext,
                                  AVFrame* aFrame);
 
   static int AllocateBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame);
   static void ReleaseBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame);
   int64_t GetPts(const AVPacket& packet);
 
-  MediaDataDecoderCallback* mCallback;
   nsRefPtr<ImageContainer> mImageContainer;
   uint32_t mDisplayWidth;
   uint32_t mDisplayHeight;
   AVCodecParserContext* mCodecParser;
 };
 
 } // namespace mozilla