Bug 1168674: [ogg] P3. Add theora MediaDataDecoder. r=me
authorBrion Vibber <brion@pobox.com>
Thu, 21 Jul 2016 11:36:47 +1000
changeset 332471 06bab9aac05e886c511e5bf799725f6d3c639d19
parent 332470 8577afd128306601718956f9e096d20eb59b5bed
child 332472 0152356f027f2be2cef08e97082ef21075f8428f
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersme
bugs1168674
milestone50.0a1
Bug 1168674: [ogg] P3. Add theora MediaDataDecoder. r=me MozReview-Commit-ID: 7ZJD21JMXBY
dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
dom/media/platforms/agnostic/TheoraDecoder.cpp
dom/media/platforms/agnostic/TheoraDecoder.h
dom/media/platforms/moz.build
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
@@ -5,40 +5,44 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AgnosticDecoderModule.h"
 #include "mozilla/Logging.h"
 #include "OpusDecoder.h"
 #include "VorbisDecoder.h"
 #include "VPXDecoder.h"
 #include "WAVDecoder.h"
+#include "TheoraDecoder.h"
 
 namespace mozilla {
 
 bool
 AgnosticDecoderModule::SupportsMimeType(const nsACString& aMimeType,
                                         DecoderDoctorDiagnostics* aDiagnostics) const
 {
   bool supports =
     VPXDecoder::IsVPX(aMimeType) ||
     OpusDataDecoder::IsOpus(aMimeType) ||
     VorbisDataDecoder::IsVorbis(aMimeType) ||
-    WaveDataDecoder::IsWave(aMimeType);
+    WaveDataDecoder::IsWave(aMimeType) ||
+    TheoraDecoder::IsTheora(aMimeType);
   MOZ_LOG(sPDMLog, LogLevel::Debug, ("Agnostic decoder %s requested type",
         supports ? "supports" : "rejects"));
   return supports;
 }
 
 already_AddRefed<MediaDataDecoder>
 AgnosticDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> m;
 
   if (VPXDecoder::IsVPX(aParams.mConfig.mMimeType)) {
     m = new VPXDecoder(aParams);
+  } else if (TheoraDecoder::IsTheora(aParams.mConfig.mMimeType)) {
+    m = new TheoraDecoder(aParams);
   }
 
   return m.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 AgnosticDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TheoraDecoder.h"
+#include "XiphExtradata.h"
+#include "gfx2DGlue.h"
+#include "nsError.h"
+#include "TimeUnits.h"
+#include "mozilla/PodOperations.h"
+
+#include <algorithm>
+
+#undef LOG
+#define LOG(arg, ...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, ("TheoraDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+
+namespace mozilla {
+
+using namespace gfx;
+using namespace layers;
+
+extern LazyLogModule gMediaDecoderLog;
+
+ogg_packet InitTheoraPacket(const unsigned char* aData, size_t aLength,
+                         bool aBOS, bool aEOS,
+                         int64_t aGranulepos, int64_t aPacketNo)
+{
+  ogg_packet packet;
+  packet.packet = const_cast<unsigned char*>(aData);
+  packet.bytes = aLength;
+  packet.b_o_s = aBOS;
+  packet.e_o_s = aEOS;
+  packet.granulepos = aGranulepos;
+  packet.packetno = aPacketNo;
+  return packet;
+}
+
+TheoraDecoder::TheoraDecoder(const CreateDecoderParams& aParams)
+  : mImageContainer(aParams.mImageContainer)
+  , mTaskQueue(aParams.mTaskQueue)
+  , mCallback(aParams.mCallback)
+  , mIsFlushing(false)
+  , mTheoraSetupInfo(nullptr)
+  , mTheoraDecoderContext(nullptr)
+  , mPacketCount(0)
+  , mInfo(aParams.VideoConfig())
+{
+  MOZ_COUNT_CTOR(TheoraDecoder);
+}
+
+TheoraDecoder::~TheoraDecoder()
+{
+  MOZ_COUNT_DTOR(TheoraDecoder);
+  th_setup_free(mTheoraSetupInfo);
+  th_comment_clear(&mTheoraComment);
+  th_info_clear(&mTheoraInfo);
+}
+
+nsresult
+TheoraDecoder::Shutdown()
+{
+  if (mTheoraDecoderContext) {
+    th_decode_free(mTheoraDecoderContext);
+    mTheoraDecoderContext = nullptr;
+  }
+  return NS_OK;
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+TheoraDecoder::Init()
+{
+  th_comment_init(&mTheoraComment);
+  th_info_init(&mTheoraInfo);
+
+  nsTArray<unsigned char*> headers;
+  nsTArray<size_t> headerLens;
+  if (!XiphExtradataToHeaders(headers, headerLens,
+      mInfo.mCodecSpecificConfig->Elements(),
+      mInfo.mCodecSpecificConfig->Length())) {
+      return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
+  }
+  for (size_t i = 0; i < headers.Length(); i++) {
+    if (NS_FAILED(DoDecodeHeader(headers[i], headerLens[i]))) {
+      return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
+    }
+  }
+  if (mPacketCount != 3) {
+    return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
+  }
+
+  mTheoraDecoderContext = th_decode_alloc(&mTheoraInfo, mTheoraSetupInfo);
+  if (mTheoraDecoderContext) {
+    return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
+  } else {
+    return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
+  }
+
+}
+
+nsresult
+TheoraDecoder::Flush()
+{
+  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+  mIsFlushing = true;
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
+    // nothing to do for now.
+  });
+  SyncRunnable::DispatchToThread(mTaskQueue, r);
+  mIsFlushing = false;
+  return NS_OK;
+}
+
+nsresult
+TheoraDecoder::DoDecodeHeader(const unsigned char* aData, size_t aLength)
+{
+  bool bos = mPacketCount == 0;
+  ogg_packet pkt = InitTheoraPacket(aData, aLength, bos, false, 0, mPacketCount++);
+
+  int r = th_decode_headerin(&mTheoraInfo,
+                             &mTheoraComment,
+                             &mTheoraSetupInfo,
+                             &pkt);
+  return r > 0 ? NS_OK : NS_ERROR_FAILURE;
+}
+
+int
+TheoraDecoder::DoDecode(MediaRawData* aSample)
+{
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
+  const unsigned char* aData = aSample->Data();
+  size_t aLength = aSample->Size();
+
+  bool bos = mPacketCount == 0;
+  ogg_packet pkt = InitTheoraPacket(aData, aLength, bos, false, aSample->mTimecode, mPacketCount++);
+
+  int ret = th_decode_packetin(mTheoraDecoderContext, &pkt, nullptr);
+  if (ret == 0 || ret == TH_DUPFRAME) {
+    th_ycbcr_buffer ycbcr;
+    th_decode_ycbcr_out(mTheoraDecoderContext, ycbcr);
+
+    int hdec = !(mTheoraInfo.pixel_fmt & 1);
+    int vdec = !(mTheoraInfo.pixel_fmt & 2);
+
+    VideoData::YCbCrBuffer b;
+    b.mPlanes[0].mData = ycbcr[0].data;
+    b.mPlanes[0].mStride = ycbcr[0].stride;
+    b.mPlanes[0].mHeight = mTheoraInfo.frame_height;
+    b.mPlanes[0].mWidth = mTheoraInfo.frame_width;
+    b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0;
+
+    b.mPlanes[1].mData = ycbcr[1].data;
+    b.mPlanes[1].mStride = ycbcr[1].stride;
+    b.mPlanes[1].mHeight = mTheoraInfo.frame_height >> vdec;
+    b.mPlanes[1].mWidth = mTheoraInfo.frame_width >> hdec;
+    b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0;
+
+    b.mPlanes[2].mData = ycbcr[2].data;
+    b.mPlanes[2].mStride = ycbcr[2].stride;
+    b.mPlanes[2].mHeight = mTheoraInfo.frame_height >> vdec;
+    b.mPlanes[2].mWidth = mTheoraInfo.frame_width >> hdec;
+    b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0;
+
+    IntRect pictureArea(mTheoraInfo.pic_x, mTheoraInfo.pic_y,
+                        mTheoraInfo.pic_width, mTheoraInfo.pic_height);
+
+    VideoInfo info;
+    info.mDisplay = mInfo.mDisplay;
+    RefPtr<VideoData> v = VideoData::Create(info,
+                                            mImageContainer,
+                                            aSample->mOffset,
+                                            aSample->mTime,
+                                            aSample->mDuration,
+                                            b,
+                                            aSample->mKeyframe,
+                                            aSample->mTimecode,
+                                            mInfo.ScaledImageRect(mTheoraInfo.frame_width,
+                                                                  mTheoraInfo.frame_height));
+    if (!v) {
+      LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld",
+          mTheoraInfo.frame_width, mTheoraInfo.frame_height, mInfo.mDisplay.width, mInfo.mDisplay.height,
+          mInfo.mImage.width, mInfo.mImage.height);
+      return -1;
+    }
+    mCallback->Output(v);
+    return 0;
+  } else {
+    LOG("Theora Decode error: %d", ret);
+    return -1;
+  }
+}
+
+void
+TheoraDecoder::ProcessDecode(MediaRawData* aSample)
+{
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+  if (mIsFlushing) {
+    return;
+  }
+  if (DoDecode(aSample) == -1) {
+    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+  } else if (mTaskQueue->IsEmpty()) {
+    mCallback->InputExhausted();
+  }
+}
+
+nsresult
+TheoraDecoder::Input(MediaRawData* aSample)
+{
+  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+  mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
+                       this, &TheoraDecoder::ProcessDecode, aSample));
+
+  return NS_OK;
+}
+
+void
+TheoraDecoder::ProcessDrain()
+{
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+  mCallback->DrainComplete();
+}
+
+nsresult
+TheoraDecoder::Drain()
+{
+  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+  mTaskQueue->Dispatch(NewRunnableMethod(this, &TheoraDecoder::ProcessDrain));
+
+  return NS_OK;
+}
+
+/* static */
+bool
+TheoraDecoder::IsTheora(const nsACString& aMimeType)
+{
+  return aMimeType.EqualsLiteral("video/ogg; codecs=theora");
+}
+
+} // namespace mozilla
+#undef LOG
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/TheoraDecoder.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(TheoraDecoder_h_)
+#define TheoraDecoder_h_
+
+#include "PlatformDecoderModule.h"
+
+#include <stdint.h>
+#include "ogg/ogg.h"
+#include "theora/theoradec.h"
+
+namespace mozilla {
+
+  using namespace layers;
+
+class TheoraDecoder : public MediaDataDecoder
+{
+public:
+  explicit TheoraDecoder(const CreateDecoderParams& aParams);
+
+  ~TheoraDecoder();
+
+  RefPtr<InitPromise> Init() override;
+  nsresult Input(MediaRawData* aSample) override;
+  nsresult Flush() override;
+  nsresult Drain() override;
+  nsresult Shutdown() override;
+
+  // Return true if mimetype is a Theora codec
+  static bool IsTheora(const nsACString& aMimeType);
+
+  const char* GetDescriptionName() const override
+  {
+    return "theora video decoder";
+  }
+
+private:
+  nsresult DoDecodeHeader(const unsigned char* aData, size_t aLength);
+
+  void ProcessDecode(MediaRawData* aSample);
+  int DoDecode(MediaRawData* aSample);
+  void ProcessDrain();
+
+  RefPtr<ImageContainer> mImageContainer;
+  RefPtr<TaskQueue> mTaskQueue;
+  MediaDataDecoderCallback* mCallback;
+  Atomic<bool> mIsFlushing;
+
+  // Theora header & decoder state
+  th_info mTheoraInfo;
+  th_comment mTheoraComment;
+  th_setup_info *mTheoraSetupInfo;
+  th_dec_ctx *mTheoraDecoderContext;
+  int mPacketCount;
+
+  const VideoInfo& mInfo;
+};
+
+} // namespace mozilla
+
+#endif
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -2,29 +2,31 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'agnostic/AgnosticDecoderModule.h',
     'agnostic/OpusDecoder.h',
+    'agnostic/TheoraDecoder.h',
     'agnostic/VorbisDecoder.h',
     'agnostic/VPXDecoder.h',
     'MediaTelemetryConstants.h',
     'PDMFactory.h',
     'PlatformDecoderModule.h',
     'wrappers/FuzzingWrapper.h',
     'wrappers/H264Converter.h'
 ]
 
 UNIFIED_SOURCES += [
     'agnostic/AgnosticDecoderModule.cpp',
     'agnostic/BlankDecoderModule.cpp',
     'agnostic/OpusDecoder.cpp',
+    'agnostic/TheoraDecoder.cpp',
     'agnostic/VorbisDecoder.cpp',
     'agnostic/VPXDecoder.cpp',
     'agnostic/WAVDecoder.cpp',
     'PDMFactory.cpp',
     'wrappers/FuzzingWrapper.cpp',
     'wrappers/H264Converter.cpp'
 ]