summaryrefslogtreecommitdiffstats
path: root/dom/media/platforms/agnostic/VPXDecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/platforms/agnostic/VPXDecoder.cpp')
-rw-r--r--dom/media/platforms/agnostic/VPXDecoder.cpp676
1 files changed, 676 insertions, 0 deletions
diff --git a/dom/media/platforms/agnostic/VPXDecoder.cpp b/dom/media/platforms/agnostic/VPXDecoder.cpp
new file mode 100644
index 0000000000..0c4d8234f5
--- /dev/null
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -0,0 +1,676 @@
+/* -*- 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 "VPXDecoder.h"
+
+#include <algorithm>
+
+#include "BitReader.h"
+#include "BitWriter.h"
+#include "ImageContainer.h"
+#include "TimeUnits.h"
+#include "gfx2DGlue.h"
+#include "gfxUtils.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Unused.h"
+#include "nsError.h"
+#include "PerformanceRecorder.h"
+#include "prsystem.h"
+#include "VideoUtils.h"
+#include "vpx/vpx_image.h"
+
+#undef LOG
+#define LOG(arg, ...) \
+ DDMOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, "::%s: " arg, __func__, \
+ ##__VA_ARGS__)
+
+namespace mozilla {
+
+using namespace gfx;
+using namespace layers;
+
+static VPXDecoder::Codec MimeTypeToCodec(const nsACString& aMimeType) {
+ if (aMimeType.EqualsLiteral("video/vp8")) {
+ return VPXDecoder::Codec::VP8;
+ } else if (aMimeType.EqualsLiteral("video/vp9")) {
+ return VPXDecoder::Codec::VP9;
+ }
+ return VPXDecoder::Codec::Unknown;
+}
+
+static nsresult InitContext(vpx_codec_ctx_t* aCtx, const VideoInfo& aInfo,
+ const VPXDecoder::Codec aCodec, bool aLowLatency) {
+ int decode_threads = 2;
+
+ vpx_codec_iface_t* dx = nullptr;
+ if (aCodec == VPXDecoder::Codec::VP8) {
+ dx = vpx_codec_vp8_dx();
+ } else if (aCodec == VPXDecoder::Codec::VP9) {
+ dx = vpx_codec_vp9_dx();
+ 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());
+
+ vpx_codec_dec_cfg_t config;
+ config.threads = aLowLatency ? 1 : decode_threads;
+ config.w = config.h = 0; // set after decode
+
+ if (!dx || vpx_codec_dec_init(aCtx, dx, &config, 0)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+VPXDecoder::VPXDecoder(const CreateDecoderParams& aParams)
+ : mImageContainer(aParams.mImageContainer),
+ mImageAllocator(aParams.mKnowsCompositor),
+ mTaskQueue(TaskQueue::Create(
+ GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), "VPXDecoder")),
+ mInfo(aParams.VideoConfig()),
+ mCodec(MimeTypeToCodec(aParams.VideoConfig().mMimeType)),
+ mLowLatency(
+ aParams.mOptions.contains(CreateDecoderParams::Option::LowLatency)),
+ mTrackingId(aParams.mTrackingId) {
+ MOZ_COUNT_CTOR(VPXDecoder);
+ PodZero(&mVPX);
+ PodZero(&mVPXAlpha);
+}
+
+VPXDecoder::~VPXDecoder() { MOZ_COUNT_DTOR(VPXDecoder); }
+
+RefPtr<ShutdownPromise> VPXDecoder::Shutdown() {
+ RefPtr<VPXDecoder> self = this;
+ return InvokeAsync(mTaskQueue, __func__, [self]() {
+ vpx_codec_destroy(&self->mVPX);
+ vpx_codec_destroy(&self->mVPXAlpha);
+ return self->mTaskQueue->BeginShutdown();
+ });
+}
+
+RefPtr<MediaDataDecoder::InitPromise> VPXDecoder::Init() {
+ if (NS_FAILED(InitContext(&mVPX, mInfo, mCodec, mLowLatency))) {
+ return VPXDecoder::InitPromise::CreateAndReject(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+ if (mInfo.HasAlpha()) {
+ if (NS_FAILED(InitContext(&mVPXAlpha, mInfo, mCodec, mLowLatency))) {
+ return VPXDecoder::InitPromise::CreateAndReject(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+ }
+ return VPXDecoder::InitPromise::CreateAndResolve(TrackInfo::kVideoTrack,
+ __func__);
+}
+
+RefPtr<MediaDataDecoder::FlushPromise> VPXDecoder::Flush() {
+ return InvokeAsync(mTaskQueue, __func__, []() {
+ return FlushPromise::CreateAndResolve(true, __func__);
+ });
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> VPXDecoder::ProcessDecode(
+ MediaRawData* aSample) {
+ MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
+
+ MediaInfoFlag flag = MediaInfoFlag::None;
+ flag |= (aSample->mKeyframe ? MediaInfoFlag::KeyFrame
+ : MediaInfoFlag::NonKeyFrame);
+ flag |= MediaInfoFlag::SoftwareDecoding;
+ switch (mCodec) {
+ case Codec::VP8:
+ flag |= MediaInfoFlag::VIDEO_VP8;
+ break;
+ case Codec::VP9:
+ flag |= MediaInfoFlag::VIDEO_VP9;
+ break;
+ default:
+ break;
+ }
+ flag |= MediaInfoFlag::VIDEO_THEORA;
+ auto rec = mTrackingId.map([&](const auto& aId) {
+ return PerformanceRecorder<DecodeStage>("VPXDecoder"_ns, aId, flag);
+ });
+
+ if (vpx_codec_err_t r = vpx_codec_decode(&mVPX, aSample->Data(),
+ aSample->Size(), nullptr, 0)) {
+ LOG("VPX Decode error: %s", vpx_codec_err_to_string(r));
+ return DecodePromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("VPX error: %s", vpx_codec_err_to_string(r))),
+ __func__);
+ }
+
+ vpx_codec_iter_t iter = nullptr;
+ vpx_image_t* img;
+ vpx_image_t* img_alpha = nullptr;
+ bool alpha_decoded = false;
+ DecodedData results;
+
+ while ((img = vpx_codec_get_frame(&mVPX, &iter))) {
+ NS_ASSERTION(img->fmt == VPX_IMG_FMT_I420 || img->fmt == VPX_IMG_FMT_I444,
+ "WebM image format not I420 or I444");
+ NS_ASSERTION(!alpha_decoded,
+ "Multiple frames per packet that contains alpha");
+
+ if (aSample->AlphaSize() > 0) {
+ if (!alpha_decoded) {
+ MediaResult rv = DecodeAlpha(&img_alpha, aSample);
+ if (NS_FAILED(rv)) {
+ return DecodePromise::CreateAndReject(rv, __func__);
+ }
+ alpha_decoded = true;
+ }
+ }
+ // 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].mSkip = 0;
+
+ b.mPlanes[1].mData = img->planes[1];
+ b.mPlanes[1].mStride = img->stride[1];
+ b.mPlanes[1].mSkip = 0;
+
+ b.mPlanes[2].mData = img->planes[2];
+ b.mPlanes[2].mStride = img->stride[2];
+ b.mPlanes[2].mSkip = 0;
+
+ if (img->fmt == VPX_IMG_FMT_I420) {
+ b.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
+
+ 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 == VPX_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("VPX Unknown image format");
+ return DecodePromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("VPX Unknown image format")),
+ __func__);
+ }
+ b.mYUVColorSpace = [&]() {
+ switch (img->cs) {
+ case VPX_CS_BT_601:
+ case VPX_CS_SMPTE_170:
+ case VPX_CS_SMPTE_240:
+ return gfx::YUVColorSpace::BT601;
+ case VPX_CS_BT_709:
+ return gfx::YUVColorSpace::BT709;
+ case VPX_CS_BT_2020:
+ return gfx::YUVColorSpace::BT2020;
+ default:
+ return DefaultColorSpace({img->d_w, img->d_h});
+ }
+ }();
+ b.mColorRange = img->range == VPX_CR_FULL_RANGE ? gfx::ColorRange::FULL
+ : gfx::ColorRange::LIMITED;
+
+ RefPtr<VideoData> v;
+ if (!img_alpha) {
+ v = VideoData::CreateAndCopyData(
+ mInfo, mImageContainer, aSample->mOffset, aSample->mTime,
+ aSample->mDuration, b, aSample->mKeyframe, aSample->mTimecode,
+ mInfo.ScaledImageRect(img->d_w, img->d_h), mImageAllocator);
+ } else {
+ VideoData::YCbCrBuffer::Plane alpha_plane;
+ alpha_plane.mData = img_alpha->planes[0];
+ alpha_plane.mStride = img_alpha->stride[0];
+ alpha_plane.mHeight = img_alpha->d_h;
+ alpha_plane.mWidth = img_alpha->d_w;
+ alpha_plane.mSkip = 0;
+ v = VideoData::CreateAndCopyData(
+ mInfo, mImageContainer, aSample->mOffset, aSample->mTime,
+ aSample->mDuration, b, alpha_plane, 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__);
+ }
+
+ rec.apply([&](auto& aRec) {
+ return aRec.Record([&](DecodeStage& aStage) {
+ aStage.SetResolution(static_cast<int>(img->d_w),
+ static_cast<int>(img->d_h));
+ auto format = [&]() -> Maybe<DecodeStage::ImageFormat> {
+ switch (img->fmt) {
+ case VPX_IMG_FMT_I420:
+ return Some(DecodeStage::YUV420P);
+ case VPX_IMG_FMT_I444:
+ return Some(DecodeStage::YUV444P);
+ default:
+ return Nothing();
+ }
+ }();
+ format.apply([&](auto& aFmt) { aStage.SetImageFormat(aFmt); });
+ aStage.SetYUVColorSpace(b.mYUVColorSpace);
+ aStage.SetColorRange(b.mColorRange);
+ aStage.SetColorDepth(b.mColorDepth);
+ });
+ });
+
+ results.AppendElement(std::move(v));
+ }
+ return DecodePromise::CreateAndResolve(std::move(results), __func__);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> VPXDecoder::Decode(
+ MediaRawData* aSample) {
+ return InvokeAsync<MediaRawData*>(mTaskQueue, this, __func__,
+ &VPXDecoder::ProcessDecode, aSample);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> VPXDecoder::Drain() {
+ return InvokeAsync(mTaskQueue, __func__, [] {
+ return DecodePromise::CreateAndResolve(DecodedData(), __func__);
+ });
+}
+
+MediaResult VPXDecoder::DecodeAlpha(vpx_image_t** aImgAlpha,
+ const MediaRawData* aSample) {
+ vpx_codec_err_t r = vpx_codec_decode(&mVPXAlpha, aSample->AlphaData(),
+ aSample->AlphaSize(), nullptr, 0);
+ if (r) {
+ LOG("VPX decode alpha error: %s", vpx_codec_err_to_string(r));
+ return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("VPX decode alpha error: %s",
+ vpx_codec_err_to_string(r)));
+ }
+
+ vpx_codec_iter_t iter = nullptr;
+
+ *aImgAlpha = vpx_codec_get_frame(&mVPXAlpha, &iter);
+ NS_ASSERTION((*aImgAlpha)->fmt == VPX_IMG_FMT_I420 ||
+ (*aImgAlpha)->fmt == VPX_IMG_FMT_I444,
+ "WebM image format not I420 or I444");
+
+ return NS_OK;
+}
+
+nsCString VPXDecoder::GetCodecName() const {
+ switch (mCodec) {
+ case Codec::VP8:
+ return "vp8"_ns;
+ case Codec::VP9:
+ return "vp9"_ns;
+ default:
+ return "unknown"_ns;
+ }
+}
+
+/* static */
+bool VPXDecoder::IsVPX(const nsACString& aMimeType, uint8_t aCodecMask) {
+ return ((aCodecMask & VPXDecoder::VP8) &&
+ aMimeType.EqualsLiteral("video/vp8")) ||
+ ((aCodecMask & VPXDecoder::VP9) &&
+ aMimeType.EqualsLiteral("video/vp9"));
+}
+
+/* static */
+bool VPXDecoder::IsVP8(const nsACString& aMimeType) {
+ return IsVPX(aMimeType, VPXDecoder::VP8);
+}
+
+/* static */
+bool VPXDecoder::IsVP9(const nsACString& aMimeType) {
+ return IsVPX(aMimeType, VPXDecoder::VP9);
+}
+
+/* static */
+bool VPXDecoder::IsKeyframe(Span<const uint8_t> aBuffer, Codec aCodec) {
+ VPXStreamInfo info;
+ return GetStreamInfo(aBuffer, info, aCodec) && info.mKeyFrame;
+}
+
+/* static */
+gfx::IntSize VPXDecoder::GetFrameSize(Span<const uint8_t> aBuffer,
+ Codec aCodec) {
+ VPXStreamInfo info;
+ if (!GetStreamInfo(aBuffer, info, aCodec)) {
+ return gfx::IntSize();
+ }
+ return info.mImage;
+}
+
+/* static */
+gfx::IntSize VPXDecoder::GetDisplaySize(Span<const uint8_t> aBuffer,
+ Codec aCodec) {
+ VPXStreamInfo info;
+ if (!GetStreamInfo(aBuffer, info, aCodec)) {
+ return gfx::IntSize();
+ }
+ return info.mDisplay;
+}
+
+/* static */
+int VPXDecoder::GetVP9Profile(Span<const uint8_t> aBuffer) {
+ VPXStreamInfo info;
+ if (!GetStreamInfo(aBuffer, info, Codec::VP9)) {
+ return -1;
+ }
+ return info.mProfile;
+}
+
+/* static */
+bool VPXDecoder::GetStreamInfo(Span<const uint8_t> aBuffer,
+ VPXDecoder::VPXStreamInfo& aInfo, Codec aCodec) {
+ if (aBuffer.IsEmpty()) {
+ // Can't be good.
+ return false;
+ }
+
+ aInfo = VPXStreamInfo();
+
+ if (aCodec == Codec::VP8) {
+ aInfo.mKeyFrame = (aBuffer[0] & 1) ==
+ 0; // frame type (0 for key frames, 1 for interframes)
+ if (!aInfo.mKeyFrame) {
+ // We can't retrieve the required information from interframes.
+ return true;
+ }
+ if (aBuffer.Length() < 10) {
+ return false;
+ }
+ uint8_t version = (aBuffer[0] >> 1) & 0x7;
+ if (version > 3) {
+ return false;
+ }
+ uint8_t start_code_byte_0 = aBuffer[3];
+ uint8_t start_code_byte_1 = aBuffer[4];
+ uint8_t start_code_byte_2 = aBuffer[5];
+ if (start_code_byte_0 != 0x9d || start_code_byte_1 != 0x01 ||
+ start_code_byte_2 != 0x2a) {
+ return false;
+ }
+ uint16_t width = (aBuffer[6] | aBuffer[7] << 8) & 0x3fff;
+ uint16_t height = (aBuffer[8] | aBuffer[9] << 8) & 0x3fff;
+
+ // aspect ratio isn't found in the VP8 frame header.
+ aInfo.mImage = gfx::IntSize(width, height);
+ aInfo.mDisplayAndImageDifferent = false;
+ aInfo.mDisplay = aInfo.mImage;
+ return true;
+ }
+
+ BitReader br(aBuffer.Elements(), aBuffer.Length() * 8);
+ uint32_t frameMarker = br.ReadBits(2); // frame_marker
+ if (frameMarker != 2) {
+ // That's not a valid vp9 header.
+ return false;
+ }
+ uint32_t profile = br.ReadBits(1); // profile_low_bit
+ profile |= br.ReadBits(1) << 1; // profile_high_bit
+ if (profile == 3) {
+ profile += br.ReadBits(1); // reserved_zero
+ if (profile > 3) {
+ // reserved_zero wasn't zero.
+ return false;
+ }
+ }
+
+ aInfo.mProfile = profile;
+
+ bool show_existing_frame = br.ReadBits(1);
+ if (show_existing_frame) {
+ if (profile == 3 && aBuffer.Length() < 2) {
+ return false;
+ }
+ Unused << br.ReadBits(3); // frame_to_show_map_idx
+ return true;
+ }
+
+ if (aBuffer.Length() < 10) {
+ // Header too small;
+ return false;
+ }
+
+ aInfo.mKeyFrame = !br.ReadBits(1);
+ bool show_frame = br.ReadBits(1);
+ bool error_resilient_mode = br.ReadBits(1);
+
+ auto frame_sync_code = [&]() -> bool {
+ uint8_t frame_sync_byte_1 = br.ReadBits(8);
+ uint8_t frame_sync_byte_2 = br.ReadBits(8);
+ uint8_t frame_sync_byte_3 = br.ReadBits(8);
+ return frame_sync_byte_1 == 0x49 && frame_sync_byte_2 == 0x83 &&
+ frame_sync_byte_3 == 0x42;
+ };
+
+ auto color_config = [&]() -> bool {
+ aInfo.mBitDepth = 8;
+ if (profile >= 2) {
+ bool ten_or_twelve_bit = br.ReadBits(1);
+ aInfo.mBitDepth = ten_or_twelve_bit ? 12 : 10;
+ }
+ aInfo.mColorSpace = br.ReadBits(3);
+ if (aInfo.mColorSpace != 7 /* CS_RGB */) {
+ aInfo.mFullRange = br.ReadBits(1);
+ if (profile == 1 || profile == 3) {
+ aInfo.mSubSampling_x = br.ReadBits(1);
+ aInfo.mSubSampling_y = br.ReadBits(1);
+ if (br.ReadBits(1)) { // reserved_zero
+ return false;
+ };
+ } else {
+ aInfo.mSubSampling_x = true;
+ aInfo.mSubSampling_y = true;
+ }
+ } else {
+ aInfo.mFullRange = true;
+ if (profile == 1 || profile == 3) {
+ aInfo.mSubSampling_x = false;
+ aInfo.mSubSampling_y = false;
+ if (br.ReadBits(1)) { // reserved_zero
+ return false;
+ };
+ } else {
+ // sRGB color space is only available with VP9 profile 1.
+ return false;
+ }
+ }
+ return true;
+ };
+
+ auto frame_size = [&]() {
+ int32_t width = static_cast<int32_t>(br.ReadBits(16)) + 1;
+ int32_t height = static_cast<int32_t>(br.ReadBits(16)) + 1;
+ aInfo.mImage = gfx::IntSize(width, height);
+ };
+
+ auto render_size = [&]() {
+ // render_and_frame_size_different
+ aInfo.mDisplayAndImageDifferent = br.ReadBits(1);
+ if (aInfo.mDisplayAndImageDifferent) {
+ int32_t width = static_cast<int32_t>(br.ReadBits(16)) + 1;
+ int32_t height = static_cast<int32_t>(br.ReadBits(16)) + 1;
+ aInfo.mDisplay = gfx::IntSize(width, height);
+ } else {
+ aInfo.mDisplay = aInfo.mImage;
+ }
+ };
+
+ if (aInfo.mKeyFrame) {
+ if (!frame_sync_code()) {
+ return false;
+ }
+ if (!color_config()) {
+ return false;
+ }
+ frame_size();
+ render_size();
+ } else {
+ bool intra_only = show_frame ? false : br.ReadBit();
+ if (!error_resilient_mode) {
+ Unused << br.ReadBits(2); // reset_frame_context
+ }
+ if (intra_only) {
+ if (!frame_sync_code()) {
+ return false;
+ }
+ if (profile > 0) {
+ if (!color_config()) {
+ return false;
+ }
+ } else {
+ aInfo.mColorSpace = 1; // CS_BT_601
+ aInfo.mSubSampling_x = true;
+ aInfo.mSubSampling_y = true;
+ aInfo.mBitDepth = 8;
+ }
+ Unused << br.ReadBits(8); // refresh_frame_flags
+ frame_size();
+ render_size();
+ }
+ }
+ return true;
+}
+
+// Ref: "VP Codec ISO Media File Format Binding, v1.0, 2017-03-31"
+// <https://www.webmproject.org/vp9/mp4/>
+//
+// class VPCodecConfigurationBox extends FullBox('vpcC', version = 1, 0)
+// {
+// VPCodecConfigurationRecord() vpcConfig;
+// }
+//
+// aligned (8) class VPCodecConfigurationRecord {
+// unsigned int (8) profile;
+// unsigned int (8) level;
+// unsigned int (4) bitDepth;
+// unsigned int (3) chromaSubsampling;
+// unsigned int (1) videoFullRangeFlag;
+// unsigned int (8) colourPrimaries;
+// unsigned int (8) transferCharacteristics;
+// unsigned int (8) matrixCoefficients;
+// unsigned int (16) codecIntializationDataSize;
+// unsigned int (8)[] codecIntializationData;
+// }
+
+/* static */
+void VPXDecoder::GetVPCCBox(MediaByteBuffer* aDestBox,
+ const VPXStreamInfo& aInfo) {
+ BitWriter writer(aDestBox);
+
+ int chroma = [&]() {
+ if (aInfo.mSubSampling_x && aInfo.mSubSampling_y) {
+ return 1; // 420 Colocated;
+ }
+ if (aInfo.mSubSampling_x && !aInfo.mSubSampling_y) {
+ return 2; // 422
+ }
+ if (!aInfo.mSubSampling_x && !aInfo.mSubSampling_y) {
+ return 3; // 444
+ }
+ // This indicates 4:4:0 subsampling, which is not expressable in the
+ // 'vpcC' box. Default to 4:2:0.
+ return 1;
+ }();
+
+ writer.WriteU8(1); // version
+ writer.WriteBits(0, 24); // flags
+
+ writer.WriteU8(aInfo.mProfile); // profile
+ writer.WriteU8(10); // level set it to 1.0
+
+ writer.WriteBits(aInfo.mBitDepth, 4); // bitdepth
+ writer.WriteBits(chroma, 3); // chroma
+ writer.WriteBit(aInfo.mFullRange); // full/restricted range
+
+ // See VPXDecoder::VPXStreamInfo enums
+ writer.WriteU8(aInfo.mColorPrimaries); // color primaries
+ writer.WriteU8(aInfo.mTransferFunction); // transfer characteristics
+ writer.WriteU8(2); // matrix coefficients: unspecified
+
+ writer.WriteBits(0,
+ 16); // codecIntializationDataSize (must be 0 for VP8/VP9)
+}
+
+/* static */
+bool VPXDecoder::SetVideoInfo(VideoInfo* aDestInfo, const nsAString& aCodec) {
+ VPXDecoder::VPXStreamInfo info;
+ uint8_t level = 0;
+ uint8_t chroma = 1;
+ VideoColorSpace colorSpace;
+ if (!ExtractVPXCodecDetails(aCodec, info.mProfile, level, info.mBitDepth,
+ chroma, colorSpace)) {
+ return false;
+ }
+
+ aDestInfo->mColorPrimaries =
+ gfxUtils::CicpToColorPrimaries(colorSpace.mPrimaries, sPDMLog);
+ aDestInfo->mTransferFunction =
+ gfxUtils::CicpToTransferFunction(colorSpace.mTransfer);
+ aDestInfo->mColorDepth = gfx::ColorDepthForBitDepth(info.mBitDepth);
+ VPXDecoder::SetChroma(info, chroma);
+ info.mFullRange = colorSpace.mRange == ColorRange::FULL;
+ RefPtr<MediaByteBuffer> extraData = new MediaByteBuffer();
+ VPXDecoder::GetVPCCBox(extraData, info);
+ aDestInfo->mExtraData = extraData;
+ return true;
+}
+
+/* static */
+void VPXDecoder::SetChroma(VPXStreamInfo& aDestInfo, uint8_t chroma) {
+ switch (chroma) {
+ case 0:
+ case 1:
+ aDestInfo.mSubSampling_x = true;
+ aDestInfo.mSubSampling_y = true;
+ break;
+ case 2:
+ aDestInfo.mSubSampling_x = true;
+ aDestInfo.mSubSampling_y = false;
+ break;
+ case 3:
+ aDestInfo.mSubSampling_x = false;
+ aDestInfo.mSubSampling_y = false;
+ break;
+ }
+}
+
+/* static */
+void VPXDecoder::ReadVPCCBox(VPXStreamInfo& aDestInfo, MediaByteBuffer* aBox) {
+ BitReader reader(aBox);
+
+ reader.ReadBits(8); // version
+ reader.ReadBits(24); // flags
+ aDestInfo.mProfile = reader.ReadBits(8);
+ reader.ReadBits(8); // level
+
+ aDestInfo.mBitDepth = reader.ReadBits(4);
+ SetChroma(aDestInfo, reader.ReadBits(3));
+ aDestInfo.mFullRange = reader.ReadBit();
+
+ aDestInfo.mColorPrimaries = reader.ReadBits(8); // color primaries
+ aDestInfo.mTransferFunction = reader.ReadBits(8); // transfer characteristics
+ reader.ReadBits(8); // matrix coefficients
+
+ MOZ_ASSERT(reader.ReadBits(16) ==
+ 0); // codecInitializationDataSize (must be 0 for VP8/VP9)
+}
+
+} // namespace mozilla
+#undef LOG