Bug 1314147 - Add AOMDecoder. r=jya
authorRalph Giles <giles@mozilla.com>
Wed, 19 Apr 2017 13:19:32 -0700
changeset 356839 95e800f66f2b5b8f99cc7c391fbc4d177a693e6f
parent 356838 c57f94d10914aa00968ac3d61f85fbf224b639c0
child 356840 d5f91c51a3ab24ccf89e3a06d77007a679ae016a
push id31775
push userihsiao@mozilla.com
push dateMon, 08 May 2017 03:10:38 +0000
treeherdermozilla-central@22aaf8bad4df [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya
bugs1314147
milestone55.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 1314147 - Add AOMDecoder. r=jya Port the VPXDecoder interface to libaom which uses the same api with the names changed. I've removed the alpha support for now. MozReview-Commit-ID: IdxcVWhNgVl
dom/media/platforms/agnostic/AOMDecoder.cpp
dom/media/platforms/agnostic/AOMDecoder.h
dom/media/platforms/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/AOMDecoder.cpp
@@ -0,0 +1,260 @@
+/* -*- 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 "AOMDecoder.h"
+#include "MediaResult.h"
+#include "TimeUnits.h"
+#include "aom/aomdx.h"
+#include "gfx2DGlue.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsError.h"
+#include "prsystem.h"
+
+#include <algorithm>
+
+#undef LOG
+#define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("AOMDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+#define LOG_RESULT(code, message, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("AOMDecoder::%s: %s (code %d) " message, __func__, aom_codec_err_to_string(code), (int)code, ##__VA_ARGS__))
+
+namespace mozilla {
+
+using namespace gfx;
+using namespace layers;
+
+
+static MediaResult
+InitContext(aom_codec_ctx_t* aCtx,
+            const VideoInfo& aInfo)
+{
+  aom_codec_iface_t* dx = aom_codec_av1_dx();
+  if (!dx) {
+    return MediaResult(NS_ERROR_FAILURE,
+                       RESULT_DETAIL("Couldn't get AV1 decoder interface."));
+  }
+
+  int decode_threads = 2;
+  if (aInfo.mDisplay.width >= 2048) {
+    decode_threads = 8;
+  }
+  else if (aInfo.mDisplay.width >= 1024) {
+    decode_threads = 4;
+  }
+  decode_threads = std::min(decode_threads, PR_GetNumberOfProcessors());
+
+  aom_codec_dec_cfg_t config;
+  PodZero(&config);
+  config.threads = decode_threads;
+  config.w = config.h = 0; // set after decode
+
+  aom_codec_flags_t flags = 0;
+
+  auto res = aom_codec_dec_init(aCtx, dx, &config, flags);
+  if (res != AOM_CODEC_OK) {
+    LOG_RESULT(res, "Codec initialization failed!");
+    return MediaResult(NS_ERROR_FAILURE,
+                       RESULT_DETAIL("AOM error initializing AV1 decoder: %s",
+                                     aom_codec_err_to_string(res)));
+  }
+  return NS_OK;
+}
+
+AOMDecoder::AOMDecoder(const CreateDecoderParams& aParams)
+  : mImageContainer(aParams.mImageContainer)
+  , mTaskQueue(aParams.mTaskQueue)
+  , mInfo(aParams.VideoConfig())
+{
+  PodZero(&mCodec);
+}
+
+AOMDecoder::~AOMDecoder()
+{
+}
+
+RefPtr<ShutdownPromise>
+AOMDecoder::Shutdown()
+{
+  RefPtr<AOMDecoder> self = this;
+  return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+    auto res = aom_codec_destroy(&mCodec);
+    if (res != AOM_CODEC_OK) {
+      LOG_RESULT(res, "aom_codec_destroy");
+    }
+    return ShutdownPromise::CreateAndResolve(true, __func__);
+  });
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+AOMDecoder::Init()
+{
+  if (NS_FAILED(InitContext(&mCodec, mInfo))) {
+    return AOMDecoder::InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                                    __func__);
+  }
+  return AOMDecoder::InitPromise::CreateAndResolve(TrackInfo::kVideoTrack,
+                                                   __func__);
+}
+
+RefPtr<MediaDataDecoder::FlushPromise>
+AOMDecoder::Flush()
+{
+  return InvokeAsync(mTaskQueue, __func__, []() {
+    return FlushPromise::CreateAndResolve(true, __func__);
+  });
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+AOMDecoder::ProcessDecode(MediaRawData* aSample)
+{
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
+#if defined(DEBUG)
+  NS_ASSERTION(IsKeyframe(*aSample) == aSample->mKeyframe,
+               "AOM Decode Keyframe error sample->mKeyframe and si.si_kf out of sync");
+#endif
+
+  if (aom_codec_err_t r = aom_codec_decode(&mCodec, aSample->Data(), aSample->Size(), nullptr, 0)) {
+    LOG_RESULT(r, "Decode error!");
+    return DecodePromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+                  RESULT_DETAIL("AOM error decoding AV1 sample: %s",
+                                aom_codec_err_to_string(r))),
+      __func__);
+  }
+
+  aom_codec_iter_t iter = nullptr;
+  aom_image_t *img;
+  DecodedData results;
+
+  while ((img = aom_codec_get_frame(&mCodec, &iter))) {
+    NS_ASSERTION(img->fmt == AOM_IMG_FMT_I420 ||
+                 img->fmt == AOM_IMG_FMT_I444,
+                 "WebM image format not I420 or I444");
+
+    // Chroma shifts are rounded down as per the decoding examples in the SDK
+    VideoData::YCbCrBuffer b;
+    b.mPlanes[0].mData = img->planes[0];
+    b.mPlanes[0].mStride = img->stride[0];
+    b.mPlanes[0].mHeight = img->d_h;
+    b.mPlanes[0].mWidth = img->d_w;
+    b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0;
+
+    b.mPlanes[1].mData = img->planes[1];
+    b.mPlanes[1].mStride = img->stride[1];
+    b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0;
+
+    b.mPlanes[2].mData = img->planes[2];
+    b.mPlanes[2].mStride = img->stride[2];
+    b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0;
+
+    if (img->fmt == AOM_IMG_FMT_I420) {
+      b.mPlanes[1].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
+      b.mPlanes[1].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
+
+      b.mPlanes[2].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
+      b.mPlanes[2].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
+    } else if (img->fmt == AOM_IMG_FMT_I444) {
+      b.mPlanes[1].mHeight = img->d_h;
+      b.mPlanes[1].mWidth = img->d_w;
+
+      b.mPlanes[2].mHeight = img->d_h;
+      b.mPlanes[2].mWidth = img->d_w;
+    } else {
+      LOG("AOM Unknown image format");
+      return DecodePromise::CreateAndReject(
+        MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+                    RESULT_DETAIL("AOM Unknown image format")),
+        __func__);
+    }
+
+    RefPtr<VideoData> v;
+    v = VideoData::CreateAndCopyData(mInfo,
+                                     mImageContainer,
+                                     aSample->mOffset,
+                                     aSample->mTime,
+                                     aSample->mDuration,
+                                     b,
+                                     aSample->mKeyframe,
+                                     aSample->mTimecode,
+                                     mInfo.ScaledImageRect(img->d_w,
+                                                           img->d_h));
+
+    if (!v) {
+      LOG(
+        "Image allocation error source %ux%u display %ux%u picture %ux%u",
+        img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height,
+        mInfo.mImage.width, mInfo.mImage.height);
+      return DecodePromise::CreateAndReject(
+        MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
+    }
+    results.AppendElement(Move(v));
+  }
+  return DecodePromise::CreateAndResolve(Move(results), __func__);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+AOMDecoder::Decode(MediaRawData* aSample)
+{
+  return InvokeAsync<MediaRawData*>(mTaskQueue, this, __func__,
+                                    &AOMDecoder::ProcessDecode, aSample);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+AOMDecoder::Drain()
+{
+  return InvokeAsync(mTaskQueue, __func__, [] {
+    return DecodePromise::CreateAndResolve(DecodedData(), __func__);
+  });
+}
+
+
+/* static */
+bool
+AOMDecoder::IsAV1(const nsACString& aMimeType)
+{
+  return aMimeType.EqualsLiteral("video/webm; codecs=av1")
+         || aMimeType.EqualsLiteral("video/av1");
+}
+
+/* static */
+bool
+AOMDecoder::IsKeyframe(Span<const uint8_t> aBuffer) {
+  aom_codec_stream_info_t info;
+  PodZero(&info);
+  info.sz = sizeof(info);
+
+  auto res = aom_codec_peek_stream_info(aom_codec_av1_dx(),
+                                        aBuffer.Elements(),
+                                        aBuffer.Length(),
+                                        &info);
+  if (res != AOM_CODEC_OK) {
+    LOG_RESULT(res, "couldn't get keyframe flag with aom_codec_peek_stream_info");
+    return false;
+  }
+
+  return bool(info.is_kf);
+}
+
+/* static */
+nsIntSize
+AOMDecoder::GetFrameSize(Span<const uint8_t> aBuffer) {
+  aom_codec_stream_info_t info;
+  PodZero(&info);
+  info.sz = sizeof(info);
+
+  auto res = aom_codec_peek_stream_info(aom_codec_av1_dx(),
+                                        aBuffer.Elements(),
+                                        aBuffer.Length(),
+                                        &info);
+  if (res != AOM_CODEC_OK) {
+    LOG_RESULT(res, "couldn't get frame size with aom_codec_peek_stream_info");
+  }
+
+  return nsIntSize(info.w, info.h);
+}
+
+} // namespace mozilla
+#undef LOG
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/AOMDecoder.h
@@ -0,0 +1,57 @@
+/* -*- 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(AOMDecoder_h_)
+#define AOMDecoder_h_
+
+#include "PlatformDecoderModule.h"
+#include "mozilla/Span.h"
+
+#include <stdint.h>
+#include "aom/aom_decoder.h"
+
+namespace mozilla {
+
+class AOMDecoder : public MediaDataDecoder
+{
+public:
+  explicit AOMDecoder(const CreateDecoderParams& aParams);
+
+  RefPtr<InitPromise> Init() override;
+  RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
+  RefPtr<DecodePromise> Drain() override;
+  RefPtr<FlushPromise> Flush() override;
+  RefPtr<ShutdownPromise> Shutdown() override;
+  const char* GetDescriptionName() const override
+  {
+    return "libaom (AV1) video decoder";
+  }
+
+  // Return true if aMimeType is a one of the strings used
+  // by our demuxers to identify AV1 streams.
+  static bool IsAV1(const nsACString& aMimeType);
+
+  // Return true if a sample is a keyframe.
+  static bool IsKeyframe(Span<const uint8_t> aBuffer);
+
+  // Return the frame dimensions for a sample.
+  static nsIntSize GetFrameSize(Span<const uint8_t> aBuffer);
+
+private:
+  ~AOMDecoder();
+  RefPtr<DecodePromise> ProcessDecode(MediaRawData* aSample);
+
+  const RefPtr<layers::ImageContainer> mImageContainer;
+  const RefPtr<TaskQueue> mTaskQueue;
+
+  // AOM decoder state
+  aom_codec_ctx_t mCodec;
+
+  const VideoInfo& mInfo;
+};
+
+} // namespace mozilla
+
+#endif // AOMDecoder_h_
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -55,16 +55,24 @@ if CONFIG['MOZ_FFVPX']:
         'ffmpeg/ffvpx',
     ]
 
 if CONFIG['MOZ_FFMPEG']:
     DIRS += [
         'ffmpeg',
     ]
 
+if CONFIG['MOZ_AV1']:
+    EXPORTS += [
+        'agnostic/AOMDecoder.h',
+    ]
+    UNIFIED_SOURCES += [
+        'agnostic/AOMDecoder.cpp',
+    ]
+
 if CONFIG['MOZ_APPLEMEDIA']:
   EXPORTS += [
       'apple/AppleDecoderModule.h',
   ]
   UNIFIED_SOURCES += [
       'apple/AppleATDecoder.cpp',
       'apple/AppleCMLinker.cpp',
       'apple/AppleDecoderModule.cpp',