Bug 1380118 - aom: Resample high bit depth frames. draft
authorRalph Giles <giles@mozilla.com>
Tue, 08 Aug 2017 17:20:36 -0700
changeset 643643 2399ab47ecfca471a31870f480cfec9a0307a641
parent 643642 a43942589979fdb9f9bf16a0f7a438280a7fd6e7
child 725360 a372925406c161a40721f7a8d8b7666ff8a79990
push id73163
push userbmo:giles@thaumas.net
push dateThu, 10 Aug 2017 00:19:36 +0000
bugs1380118
milestone57.0a1
Bug 1380118 - aom: Resample high bit depth frames. The libaom av1 decoder can return high bit depth frame data now. Handle those frames by downsampling them to 8 bits per channel so they can be passed to our normal playback pipeline. MozReview-Commit-ID: 97XYeh3YvQw
dom/media/platforms/agnostic/AOMDecoder.cpp
--- a/dom/media/platforms/agnostic/AOMDecoder.cpp
+++ b/dom/media/platforms/agnostic/AOMDecoder.cpp
@@ -3,16 +3,17 @@
 /* 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 "aom/aom_image.h"
 #include "gfx2DGlue.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/SyncRunnable.h"
 #include "nsError.h"
 #include "prsystem.h"
 #include "ImageContainer.h"
 
 #include <algorithm>
@@ -102,16 +103,67 @@ AOMDecoder::Init()
 RefPtr<MediaDataDecoder::FlushPromise>
 AOMDecoder::Flush()
 {
   return InvokeAsync(mTaskQueue, __func__, []() {
     return FlushPromise::CreateAndResolve(true, __func__);
   });
 }
 
+// Ported from third_party/aom/tools_common.c.
+static aom_codec_err_t
+highbd_img_downshift(aom_image_t *dst, aom_image_t *src, int down_shift) {
+  int plane;
+  if (dst->d_w != src->d_w || dst->d_h != src->d_h)
+    return AOM_CODEC_INVALID_PARAM;
+  if (dst->x_chroma_shift != src->x_chroma_shift)
+    return AOM_CODEC_INVALID_PARAM;
+  if (dst->y_chroma_shift != src->y_chroma_shift)
+    return AOM_CODEC_INVALID_PARAM;
+  if (dst->fmt != (src->fmt & ~AOM_IMG_FMT_HIGHBITDEPTH))
+    return AOM_CODEC_INVALID_PARAM;
+  if (down_shift < 0)
+      return AOM_CODEC_INVALID_PARAM;
+  switch (dst->fmt) {
+    case AOM_IMG_FMT_I420:
+    case AOM_IMG_FMT_I422:
+    case AOM_IMG_FMT_I444:
+    case AOM_IMG_FMT_I440:
+      break;
+    default:
+      return AOM_CODEC_INVALID_PARAM;
+  }
+  switch (src->fmt) {
+    case AOM_IMG_FMT_I42016:
+    case AOM_IMG_FMT_I42216:
+    case AOM_IMG_FMT_I44416:
+    case AOM_IMG_FMT_I44016:
+      break;
+    default:
+      return AOM_CODEC_UNSUP_BITSTREAM;
+  }
+  for (plane = 0; plane < 3; plane++) {
+    int w = src->d_w;
+    int h = src->d_h;
+    int x, y;
+    if (plane) {
+      w = (w + src->x_chroma_shift) >> src->x_chroma_shift;
+      h = (h + src->y_chroma_shift) >> src->y_chroma_shift;
+    }
+    for (y = 0; y < h; y++) {
+      uint16_t *p_src =
+          (uint16_t *)(src->planes[plane] + y * src->stride[plane]);
+      uint8_t *p_dst =
+          dst->planes[plane] + y * dst->stride[plane];
+      for (x = 0; x < w; x++) *p_dst++ = (*p_src++ >> down_shift) & 0xFF;
+    }
+  }
+  return AOM_CODEC_OK;
+}
+
 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");
@@ -123,22 +175,49 @@ AOMDecoder::ProcessDecode(MediaRawData* 
       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;
+  UniquePtr<aom_image_t> img8;
   DecodedData results;
 
   while ((img = aom_codec_get_frame(&mCodec, &iter))) {
+    if (img->fmt & AOM_IMG_FMT_HIGHBITDEPTH) {
+      // Downsample images with more than 8 bits per channel.
+      aom_img_fmt_t fmt8 = static_cast<aom_img_fmt_t>(img->fmt ^ AOM_IMG_FMT_HIGHBITDEPTH);
+      img8.reset(aom_img_alloc(NULL, fmt8, img->d_w, img->d_h, 16));
+      if (img8 == nullptr) {
+        LOG("Couldn't allocate bitdepth reduction target!");
+        return DecodePromise::CreateAndReject(
+          MediaResult(NS_ERROR_OUT_OF_MEMORY,
+                      RESULT_DETAIL("Couldn't allocate conversion buffer for AV1 frame")),
+                      __func__);
+      }
+      if (aom_codec_err_t r = highbd_img_downshift(img8.get(), img, img->bit_depth - 8)) {
+        LOG_RESULT(r, "Image downconversion failed");
+        return DecodePromise::CreateAndReject(
+          MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+                      RESULT_DETAIL("Error converting AV1 frame to 8 bits: %s",
+                                    aom_codec_err_to_string(r))),
+          __func__);
+      }
+      // img normally points to storage owned by mCodec, so it is not freed.
+      // To copy out the contents of img8 we can overwrite img with an alias.
+      // Since img is assigned at the start of the while loop and img8 is held
+      // outside that loop, the alias won't outlive the storage it points to.
+      img = img8.get();
+    }
+
     NS_ASSERTION(img->fmt == AOM_IMG_FMT_I420 ||
                  img->fmt == AOM_IMG_FMT_I444,
-                 "WebM image format not I420 or I444");
+                 "AV1 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;