summaryrefslogtreecommitdiffstats
path: root/dom/media/platforms/agnostic/AOMDecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/platforms/agnostic/AOMDecoder.cpp')
-rw-r--r--dom/media/platforms/agnostic/AOMDecoder.cpp1066
1 files changed, 1066 insertions, 0 deletions
diff --git a/dom/media/platforms/agnostic/AOMDecoder.cpp b/dom/media/platforms/agnostic/AOMDecoder.cpp
new file mode 100644
index 0000000000..d6b0576cd2
--- /dev/null
+++ b/dom/media/platforms/agnostic/AOMDecoder.cpp
@@ -0,0 +1,1066 @@
+/* -*- 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 <algorithm>
+
+#include "BitWriter.h"
+#include "BitReader.h"
+#include "ImageContainer.h"
+#include "MediaResult.h"
+#include "TimeUnits.h"
+#include "aom/aom_image.h"
+#include "aom/aomdx.h"
+#include "gfx2DGlue.h"
+#include "gfxUtils.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/TaskQueue.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+#include "prsystem.h"
+#include "VideoUtils.h"
+
+#undef LOG
+#define LOG(arg, ...) \
+ DDMOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, "::%s: " arg, __func__, \
+ ##__VA_ARGS__)
+#define LOG_RESULT(code, message, ...) \
+ DDMOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, "::%s: %s (code %d) " message, \
+ __func__, aom_codec_err_to_string(code), (int)code, ##__VA_ARGS__)
+#define LOGEX_RESULT(_this, code, message, ...) \
+ DDMOZ_LOGEX(_this, sPDMLog, mozilla::LogLevel::Debug, \
+ "::%s: %s (code %d) " message, __func__, \
+ aom_codec_err_to_string(code), (int)code, ##__VA_ARGS__)
+#define LOG_STATIC_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__))
+
+#define ASSERT_BYTE_ALIGNED(bitIO) MOZ_ASSERT((bitIO).BitCount() % 8 == 0)
+
+namespace mozilla {
+
+using namespace gfx;
+using namespace layers;
+using gfx::CICP::ColourPrimaries;
+using gfx::CICP::MatrixCoefficients;
+using gfx::CICP::TransferCharacteristics;
+
+static MediaResult InitContext(AOMDecoder& aAOMDecoder, aom_codec_ctx_t* aCtx,
+ const VideoInfo& aInfo) {
+ aom_codec_iface_t* dx = aom_codec_av1_dx();
+ if (!dx) {
+ return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Couldn't get AV1 decoder interface."));
+ }
+
+ size_t 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, GetNumberOfProcessors());
+
+ aom_codec_dec_cfg_t config;
+ PodZero(&config);
+ config.threads = static_cast<unsigned int>(decode_threads);
+ config.w = config.h = 0; // set after decode
+ config.allow_lowbitdepth = true;
+
+ aom_codec_flags_t flags = 0;
+
+ auto res = aom_codec_dec_init(aCtx, dx, &config, flags);
+ if (res != AOM_CODEC_OK) {
+ LOGEX_RESULT(&aAOMDecoder, res, "Codec initialization failed, res=%d",
+ int(res));
+ return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ 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(TaskQueue::Create(
+ GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), "AOMDecoder")),
+ mInfo(aParams.VideoConfig()),
+ mTrackingId(aParams.mTrackingId) {
+ PodZero(&mCodec);
+}
+
+AOMDecoder::~AOMDecoder() = default;
+
+RefPtr<ShutdownPromise> AOMDecoder::Shutdown() {
+ RefPtr<AOMDecoder> self = this;
+ return InvokeAsync(mTaskQueue, __func__, [self]() {
+ auto res = aom_codec_destroy(&self->mCodec);
+ if (res != AOM_CODEC_OK) {
+ LOGEX_RESULT(self.get(), res, "aom_codec_destroy");
+ }
+ return self->mTaskQueue->BeginShutdown();
+ });
+}
+
+RefPtr<MediaDataDecoder::InitPromise> AOMDecoder::Init() {
+ MediaResult rv = InitContext(*this, &mCodec, mInfo);
+ if (NS_FAILED(rv)) {
+ return AOMDecoder::InitPromise::CreateAndReject(rv, __func__);
+ }
+ return AOMDecoder::InitPromise::CreateAndResolve(TrackInfo::kVideoTrack,
+ __func__);
+}
+
+RefPtr<MediaDataDecoder::FlushPromise> AOMDecoder::Flush() {
+ return InvokeAsync(mTaskQueue, __func__, [this, self = RefPtr(this)]() {
+ mPerformanceRecorder.Record(std::numeric_limits<int64_t>::max());
+ return FlushPromise::CreateAndResolve(true, __func__);
+ });
+}
+
+// UniquePtr dtor wrapper for aom_image_t.
+struct AomImageFree {
+ void operator()(aom_image_t* img) { aom_img_free(img); }
+};
+
+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
+
+ MediaInfoFlag flag = MediaInfoFlag::None;
+ flag |= (aSample->mKeyframe ? MediaInfoFlag::KeyFrame
+ : MediaInfoFlag::NonKeyFrame);
+ flag |= MediaInfoFlag::SoftwareDecoding;
+ flag |= MediaInfoFlag::VIDEO_AV1;
+
+ mTrackingId.apply([&](const auto& aId) {
+ mPerformanceRecorder.Start(aSample->mTimecode.ToMicroseconds(),
+ "AOMDecoder"_ns, aId, flag);
+ });
+
+ if (aom_codec_err_t r = aom_codec_decode(&mCodec, aSample->Data(),
+ aSample->Size(), nullptr)) {
+ 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;
+ UniquePtr<aom_image_t, AomImageFree> img8;
+ DecodedData results;
+
+ while ((img = aom_codec_get_frame(&mCodec, &iter))) {
+ NS_ASSERTION(
+ img->fmt == AOM_IMG_FMT_I420 || img->fmt == AOM_IMG_FMT_I42016 ||
+ img->fmt == AOM_IMG_FMT_I444 || img->fmt == AOM_IMG_FMT_I44416,
+ "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].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 == AOM_IMG_FMT_I420 || img->fmt == AOM_IMG_FMT_I42016) {
+ 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 == AOM_IMG_FMT_I444 || img->fmt == AOM_IMG_FMT_I44416) {
+ 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__);
+ }
+
+ if (img->bit_depth == 10) {
+ b.mColorDepth = ColorDepth::COLOR_10;
+ } else if (img->bit_depth == 12) {
+ b.mColorDepth = ColorDepth::COLOR_12;
+ }
+
+ switch (img->mc) {
+ case AOM_CICP_MC_BT_601:
+ b.mYUVColorSpace = YUVColorSpace::BT601;
+ break;
+ case AOM_CICP_MC_BT_2020_NCL:
+ case AOM_CICP_MC_BT_2020_CL:
+ b.mYUVColorSpace = YUVColorSpace::BT2020;
+ break;
+ case AOM_CICP_MC_BT_709:
+ b.mYUVColorSpace = YUVColorSpace::BT709;
+ break;
+ default:
+ b.mYUVColorSpace = DefaultColorSpace({img->d_w, img->d_h});
+ break;
+ }
+ b.mColorRange = img->range == AOM_CR_FULL_RANGE ? ColorRange::FULL
+ : ColorRange::LIMITED;
+
+ switch (img->cp) {
+ case AOM_CICP_CP_BT_709:
+ b.mColorPrimaries = ColorSpace2::BT709;
+ break;
+ case AOM_CICP_CP_BT_2020:
+ b.mColorPrimaries = ColorSpace2::BT2020;
+ break;
+ default:
+ b.mColorPrimaries = ColorSpace2::BT709;
+ break;
+ }
+
+ 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), nullptr);
+
+ 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__);
+ }
+ mPerformanceRecorder.Record(
+ aSample->mTimecode.ToMicroseconds(), [&](DecodeStage& aStage) {
+ aStage.SetResolution(mInfo.mImage.width, mInfo.mImage.height);
+ auto format = [&]() -> Maybe<DecodeStage::ImageFormat> {
+ switch (img->fmt) {
+ case AOM_IMG_FMT_I420:
+ case AOM_IMG_FMT_I42016:
+ return Some(DecodeStage::YUV420P);
+ case AOM_IMG_FMT_I444:
+ case AOM_IMG_FMT_I44416:
+ 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> 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/av1");
+}
+
+/* static */
+bool AOMDecoder::IsKeyframe(Span<const uint8_t> aBuffer) {
+ aom_codec_stream_info_t info;
+ PodZero(&info);
+
+ auto res = aom_codec_peek_stream_info(aom_codec_av1_dx(), aBuffer.Elements(),
+ aBuffer.Length(), &info);
+ if (res != AOM_CODEC_OK) {
+ LOG_STATIC_RESULT(
+ res, "couldn't get keyframe flag with aom_codec_peek_stream_info");
+ return false;
+ }
+
+ return bool(info.is_kf);
+}
+
+/* static */
+gfx::IntSize AOMDecoder::GetFrameSize(Span<const uint8_t> aBuffer) {
+ aom_codec_stream_info_t info;
+ PodZero(&info);
+
+ auto res = aom_codec_peek_stream_info(aom_codec_av1_dx(), aBuffer.Elements(),
+ aBuffer.Length(), &info);
+ if (res != AOM_CODEC_OK) {
+ LOG_STATIC_RESULT(
+ res, "couldn't get frame size with aom_codec_peek_stream_info");
+ }
+
+ return gfx::IntSize(info.w, info.h);
+}
+
+/* static */
+AOMDecoder::OBUIterator AOMDecoder::ReadOBUs(const Span<const uint8_t>& aData) {
+ return OBUIterator(aData);
+}
+
+void AOMDecoder::OBUIterator::UpdateNext() {
+ // If mGoNext is not set, we don't need to load a new OBU.
+ if (!mGoNext) {
+ return;
+ }
+ // Check if we've reached the end of the data. Allow mGoNext to stay true so
+ // that HasNext() will return false.
+ if (mPosition >= mData.Length()) {
+ return;
+ }
+ mGoNext = false;
+
+ // If retrieving the next OBU fails, reset the current OBU and set the
+ // position past the end of the data so that HasNext() returns false.
+ auto resetExit = MakeScopeExit([&]() {
+ mCurrent = OBUInfo();
+ mPosition = mData.Length();
+ });
+
+ auto subspan = mData.Subspan(mPosition, mData.Length() - mPosition);
+ BitReader br(subspan.Elements(), subspan.Length() * 8);
+ OBUInfo temp;
+
+ // AV1 spec available at:
+ // https://aomediacodec.github.io/av1-spec/
+ // or https://aomediacodec.github.io/av1-spec/av1-spec.pdf
+
+ // begin open_bitstream_unit( )
+ // https://aomediacodec.github.io/av1-spec/#general-obu-syntax
+
+ // begin obu_header( )
+ // https://aomediacodec.github.io/av1-spec/#obu-header-syntax
+ br.ReadBit(); // obu_forbidden_bit
+ temp.mType = static_cast<OBUType>(br.ReadBits(4));
+ if (!temp.IsValid()) {
+ // Non-fatal error, unknown OBUs can be skipped as long as the size field
+ // is properly specified.
+ NS_WARNING(nsPrintfCString("Encountered unknown OBU type (%" PRIu8
+ ", OBU may be invalid",
+ static_cast<uint8_t>(temp.mType))
+ .get());
+ }
+ temp.mExtensionFlag = br.ReadBit();
+ bool hasSizeField = br.ReadBit();
+ br.ReadBit(); // obu_reserved_1bit
+
+ // begin obu_extension_header( ) (5.3.3)
+ if (temp.mExtensionFlag) {
+ if (br.BitsLeft() < 8) {
+ mResult = MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ "Not enough bits left for an OBU extension header");
+ return;
+ }
+ br.ReadBits(3); // temporal_id
+ br.ReadBits(2); // spatial_id
+ br.ReadBits(3); // extension_header_reserved_3bits
+ }
+ // end obu_extension_header( )
+ // end obu_header( )
+
+ // Get the size of the remaining OBU data attached to the header in
+ // bytes.
+ size_t size;
+ if (hasSizeField) {
+ if (br.BitsLeft() < 8) {
+ mResult = MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ "Not enough bits left for an OBU size field");
+ return;
+ }
+ CheckedUint32 checkedSize = br.ReadULEB128().toChecked<uint32_t>();
+ // Spec requires that the value ULEB128 reads is (1 << 32) - 1 or below.
+ // See leb128(): https://aomediacodec.github.io/av1-spec/#leb128
+ if (!checkedSize.isValid()) {
+ mResult =
+ MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, "OBU size was too large");
+ return;
+ }
+ size = checkedSize.value();
+ } else {
+ // This case should rarely happen in practice. To support the Annex B
+ // format in the specification, we would have to parse every header type
+ // to skip over them, but this allows us to at least iterate once to
+ // retrieve the first OBU in the data.
+ size = mData.Length() - 1 - temp.mExtensionFlag;
+ }
+
+ if (br.BitsLeft() / 8 < size) {
+ mResult = MediaResult(
+ NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ nsPrintfCString("Size specified by the OBU header (%zu) is more "
+ "than the actual remaining OBU data (%zu)",
+ size, br.BitsLeft() / 8)
+ .get());
+ return;
+ }
+
+ ASSERT_BYTE_ALIGNED(br);
+
+ size_t bytes = br.BitCount() / 8;
+ temp.mContents = mData.Subspan(mPosition + bytes, size);
+ mCurrent = temp;
+ // end open_bitstream_unit( )
+
+ mPosition += bytes + size;
+ resetExit.release();
+ mResult = NS_OK;
+}
+
+/* static */
+already_AddRefed<MediaByteBuffer> AOMDecoder::CreateOBU(
+ const OBUType aType, const Span<const uint8_t>& aContents) {
+ RefPtr<MediaByteBuffer> buffer = new MediaByteBuffer();
+
+ BitWriter bw(buffer);
+ bw.WriteBits(0, 1); // obu_forbidden_bit
+ bw.WriteBits(static_cast<uint8_t>(aType), 4);
+ bw.WriteBit(false); // obu_extension_flag
+ bw.WriteBit(true); // obu_has_size_field
+ bw.WriteBits(0, 1); // obu_reserved_1bit
+ ASSERT_BYTE_ALIGNED(bw);
+ bw.WriteULEB128(aContents.Length());
+ ASSERT_BYTE_ALIGNED(bw);
+
+ buffer->AppendElements(aContents.Elements(), aContents.Length());
+ return buffer.forget();
+}
+
+/* static */
+MediaResult AOMDecoder::ReadSequenceHeaderInfo(
+ const Span<const uint8_t>& aSample, AV1SequenceInfo& aDestInfo) {
+ // We need to get the last sequence header OBU, the specification does not
+ // limit a temporal unit to one sequence header.
+ OBUIterator iter = ReadOBUs(aSample);
+ OBUInfo seqOBU;
+
+ while (true) {
+ if (!iter.HasNext()) {
+ // Pass along the error from parsing the OBU.
+ MediaResult result = iter.GetResult();
+ if (result.Code() != NS_OK) {
+ return result;
+ }
+ break;
+ }
+ OBUInfo obu = iter.Next();
+ if (obu.mType == OBUType::SequenceHeader) {
+ seqOBU = obu;
+ }
+ }
+
+ if (seqOBU.mType != OBUType::SequenceHeader) {
+ return NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA;
+ }
+
+ // Sequence header syntax is specified here:
+ // https://aomediacodec.github.io/av1-spec/#sequence-header-obu-syntax
+ // Section 5.5: Sequence header OBU syntax
+
+ // See also Section 6.4: Sequence header OBU semantics
+ // https://aomediacodec.github.io/av1-spec/#sequence-header-obu-semantics
+ // This section defines all the fields used in the sequence header.
+ BitReader br(seqOBU.mContents.Elements(), seqOBU.mContents.Length() * 8);
+ AV1SequenceInfo tempInfo;
+
+ // begin sequence_header_obu( )
+ // https://aomediacodec.github.io/av1-spec/#general-sequence-header-obu-syntax
+ tempInfo.mProfile = br.ReadBits(3);
+ const bool stillPicture = br.ReadBit();
+ const bool reducedStillPicture = br.ReadBit();
+ if (!stillPicture && reducedStillPicture) {
+ return MediaResult(
+ NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ "reduced_still_picture is true while still_picture is false");
+ }
+
+ if (reducedStillPicture) {
+ OperatingPoint op;
+ op.mLayers = 0;
+ op.mLevel = br.ReadBits(5); // seq_level_idx[0]
+ op.mTier = 0;
+ tempInfo.mOperatingPoints.SetCapacity(1);
+ tempInfo.mOperatingPoints.AppendElement(op);
+ } else {
+ bool decoderModelInfoPresent;
+ uint8_t operatingPointCountMinusOne;
+
+ if (br.ReadBit()) { // timing_info_present_flag
+ // begin timing_info( )
+ // https://aomediacodec.github.io/av1-spec/#timing-info-syntax
+ br.ReadBits(32); // num_units_in_display_tick
+ br.ReadBits(32); // time_scale
+ if (br.ReadBit()) { // equal_picture_interval
+ br.ReadUE(); // num_ticks_per_picture_minus_1
+ }
+ // end timing_info( )
+
+ decoderModelInfoPresent = br.ReadBit();
+ if (decoderModelInfoPresent) {
+ // begin decoder_model_info( )
+ // https://aomediacodec.github.io/av1-spec/#decoder-model-info-syntax
+ br.ReadBits(5); // buffer_delay_length_minus_1
+ br.ReadBits(32); // num_units_in_decoding_tick
+ br.ReadBits(5); // buffer_removal_time_length_minus_1
+ br.ReadBits(5); // frame_presentation_time_length_minus_1
+ // end decoder_model_info( )
+ }
+ } else {
+ decoderModelInfoPresent = false;
+ }
+
+ bool initialDisplayDelayPresent = br.ReadBit();
+ operatingPointCountMinusOne = br.ReadBits(5);
+ tempInfo.mOperatingPoints.SetCapacity(operatingPointCountMinusOne + 1);
+ for (uint8_t i = 0; i <= operatingPointCountMinusOne; i++) {
+ OperatingPoint op;
+ op.mLayers = br.ReadBits(12); // operating_point_idc[ i ]
+ op.mLevel = br.ReadBits(5); // seq_level_idx[ i ]
+ op.mTier = op.mLevel > 7 ? br.ReadBits(1) : 0;
+ if (decoderModelInfoPresent) {
+ br.ReadBit(); // decoder_model_present_for_this_op[ i ]
+ }
+ if (initialDisplayDelayPresent) {
+ if (br.ReadBit()) { // initial_display_delay_present_for_this_op[ i ]
+ br.ReadBits(4);
+ }
+ }
+ tempInfo.mOperatingPoints.AppendElement(op);
+ }
+ }
+
+ uint8_t frameWidthBits = br.ReadBits(4) + 1;
+ uint8_t frameHeightBits = br.ReadBits(4) + 1;
+ uint32_t maxWidth = br.ReadBits(frameWidthBits) + 1;
+ uint32_t maxHeight = br.ReadBits(frameHeightBits) + 1;
+ tempInfo.mImage = gfx::IntSize(maxWidth, maxHeight);
+
+ if (!reducedStillPicture) {
+ if (br.ReadBit()) { // frame_id_numbers_present_flag
+ br.ReadBits(4); // delta_frame_id_length_minus_2
+ br.ReadBits(3); // additional_frame_id_legnth_minus_1
+ }
+ }
+
+ br.ReadBit(); // use_128x128_superblock
+ br.ReadBit(); // enable_filter_intra
+ br.ReadBit(); // enable_intra_edge_filter
+
+ if (reducedStillPicture) {
+ // enable_interintra_compound = 0
+ // enable_masked_compound = 0
+ // enable_warped_motion = 0
+ // enable_dual_filter = 0
+ // enable_order_hint = 0
+ // enable_jnt_comp = 0
+ // enable_ref_frame_mvs = 0
+ // seq_force_screen_content_tools = SELECT_SCREEN_CONTENT_TOOLS
+ // seq_force_integer_mv = SELECT_INTEGER_MV
+ // OrderHintBits = 0
+ } else {
+ br.ReadBit(); // enable_interintra_compound
+ br.ReadBit(); // enable_masked_compound
+ br.ReadBit(); // enable_warped_motion
+ br.ReadBit(); // enable_dual_filter
+
+ const bool enableOrderHint = br.ReadBit();
+
+ if (enableOrderHint) {
+ br.ReadBit(); // enable_jnt_comp
+ br.ReadBit(); // enable_ref_frame_mvs
+ }
+
+ uint8_t forceScreenContentTools;
+
+ if (br.ReadBit()) { // seq_choose_screen_content_tools
+ forceScreenContentTools = 2; // SELECT_SCREEN_CONTENT_TOOLS
+ } else {
+ forceScreenContentTools = br.ReadBits(1);
+ }
+
+ if (forceScreenContentTools > 0) {
+ if (!br.ReadBit()) { // seq_choose_integer_mv
+ br.ReadBit(); // seq_force_integer_mv
+ }
+ }
+
+ if (enableOrderHint) {
+ br.ReadBits(3); // order_hint_bits_minus_1
+ }
+ }
+
+ br.ReadBit(); // enable_superres
+ br.ReadBit(); // enable_cdef
+ br.ReadBit(); // enable_restoration
+
+ // begin color_config( )
+ // https://aomediacodec.github.io/av1-spec/#color-config-syntax
+ const bool highBitDepth = br.ReadBit();
+ if (tempInfo.mProfile == 2 && highBitDepth) {
+ const bool twelveBit = br.ReadBit();
+ tempInfo.mBitDepth = twelveBit ? 12 : 10;
+ } else {
+ tempInfo.mBitDepth = highBitDepth ? 10 : 8;
+ }
+
+ tempInfo.mMonochrome = tempInfo.mProfile == 1 ? false : br.ReadBit();
+
+ VideoColorSpace* colors = &tempInfo.mColorSpace;
+
+ if (br.ReadBit()) { // color_description_present_flag
+ colors->mPrimaries = static_cast<ColourPrimaries>(br.ReadBits(8));
+ colors->mTransfer = static_cast<TransferCharacteristics>(br.ReadBits(8));
+ colors->mMatrix = static_cast<MatrixCoefficients>(br.ReadBits(8));
+ } else {
+ colors->mPrimaries = ColourPrimaries::CP_UNSPECIFIED;
+ colors->mTransfer = TransferCharacteristics::TC_UNSPECIFIED;
+ colors->mMatrix = MatrixCoefficients::MC_UNSPECIFIED;
+ }
+
+ if (tempInfo.mMonochrome) {
+ colors->mRange = br.ReadBit() ? ColorRange::FULL : ColorRange::LIMITED;
+ tempInfo.mSubsamplingX = true;
+ tempInfo.mSubsamplingY = true;
+ tempInfo.mChromaSamplePosition = ChromaSamplePosition::Unknown;
+ } else if (colors->mPrimaries == ColourPrimaries::CP_BT709 &&
+ colors->mTransfer == TransferCharacteristics::TC_SRGB &&
+ colors->mMatrix == MatrixCoefficients::MC_IDENTITY) {
+ colors->mRange = ColorRange::FULL;
+ tempInfo.mSubsamplingX = false;
+ tempInfo.mSubsamplingY = false;
+ } else {
+ colors->mRange = br.ReadBit() ? ColorRange::FULL : ColorRange::LIMITED;
+ switch (tempInfo.mProfile) {
+ case 0:
+ tempInfo.mSubsamplingX = true;
+ tempInfo.mSubsamplingY = true;
+ break;
+ case 1:
+ tempInfo.mSubsamplingX = false;
+ tempInfo.mSubsamplingY = false;
+ break;
+ case 2:
+ if (tempInfo.mBitDepth == 12) {
+ tempInfo.mSubsamplingX = br.ReadBit();
+ tempInfo.mSubsamplingY =
+ tempInfo.mSubsamplingX ? br.ReadBit() : false;
+ } else {
+ tempInfo.mSubsamplingX = true;
+ tempInfo.mSubsamplingY = false;
+ }
+ break;
+ }
+ tempInfo.mChromaSamplePosition =
+ tempInfo.mSubsamplingX && tempInfo.mSubsamplingY
+ ? static_cast<ChromaSamplePosition>(br.ReadBits(2))
+ : ChromaSamplePosition::Unknown;
+ }
+
+ br.ReadBit(); // separate_uv_delta_q
+ // end color_config( )
+
+ br.ReadBit(); // film_grain_params_present
+ // end sequence_header_obu( )
+
+ // begin trailing_bits( )
+ // https://aomediacodec.github.io/av1-spec/#trailing-bits-syntax
+ if (br.BitsLeft() > 8) {
+ NS_WARNING(
+ "AV1 sequence header finished reading with more than "
+ "a byte of aligning bits, may indicate an error");
+ }
+ // Ensure that data is read correctly by checking trailing bits.
+ bool correct = br.ReadBit();
+ correct &= br.ReadBits(br.BitsLeft() % 8) == 0;
+ while (br.BitsLeft() > 0) {
+ correct &= br.ReadBits(8) == 0;
+ }
+ if (!correct) {
+ return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ "AV1 sequence header was corrupted");
+ }
+ // end trailing_bits( )
+
+ aDestInfo = tempInfo;
+ return NS_OK;
+}
+
+/* static */
+already_AddRefed<MediaByteBuffer> AOMDecoder::CreateSequenceHeader(
+ const AV1SequenceInfo& aInfo, nsresult& aResult) {
+ aResult = NS_ERROR_FAILURE;
+
+ RefPtr<MediaByteBuffer> seqHdrBuffer = new MediaByteBuffer();
+ BitWriter bw(seqHdrBuffer);
+
+ // See 5.5.1: General sequence header OBU syntax
+ // https://aomediacodec.github.io/av1-spec/#general-sequence-header-obu-syntax
+ bw.WriteBits(aInfo.mProfile, 3);
+ bw.WriteBit(false); // still_picture
+ bw.WriteBit(false); // reduced_still_picture_header
+
+ bw.WriteBit(false); // timing_info_present_flag
+ // if ( timing_info_present_flag ) {...}
+ bw.WriteBit(false); // initial_display_delay_present_flag
+
+ size_t opCount = aInfo.mOperatingPoints.Length();
+ bw.WriteBits(opCount - 1, 5); // operating_points_cnt_minus_1
+ for (size_t i = 0; i < opCount; i++) {
+ OperatingPoint op = aInfo.mOperatingPoints[i];
+ bw.WriteBits(op.mLayers, 12); // operating_point_idc[ i ]
+ bw.WriteBits(op.mLevel, 5);
+ if (op.mLevel > 7) {
+ bw.WriteBits(op.mTier, 1);
+ } else {
+ // seq_tier[ i ] = 0
+ if (op.mTier != 0) {
+ NS_WARNING("Operating points cannot specify tier for levels under 8.");
+ return nullptr;
+ }
+ }
+ // if ( decoder_model_info_present_flag ) {...}
+ // else
+ // decoder_model_info_present_for_this_op[ i ] = 0
+ // if ( initial_display_delay_present_flag ) {...}
+ }
+
+ if (!aInfo.mImage.IsEmpty() <= 0) {
+ NS_WARNING("Sequence header requires a valid image size");
+ return nullptr;
+ }
+ auto getBits = [](int32_t value) {
+ uint8_t bit = 0;
+ do {
+ value >>= 1;
+ bit++;
+ } while (value > 0);
+ return bit;
+ };
+ uint8_t bitsW = getBits(aInfo.mImage.Width());
+ uint8_t bitsH = getBits(aInfo.mImage.Height());
+ bw.WriteBits(bitsW - 1, 4);
+ bw.WriteBits(bitsH - 1, 4);
+ bw.WriteBits(aInfo.mImage.Width() - 1, bitsW);
+ bw.WriteBits(aInfo.mImage.Height() - 1, bitsH);
+
+ // if ( !reduced_still_picture_header )
+ bw.WriteBit(false); // frame_id_numbers_present_flag
+ // if ( frame_id_numbers_present_flag ) {...}
+ // end if ( !reduced_still_picture_header )
+
+ // Values below are derived from a 1080p YouTube AV1 stream.
+ // The values are unused currently for determining the usable
+ // decoder, and are only included to allow successful validation
+ // of the generated sequence header.
+
+ bw.WriteBit(true); // use_128x128_superblock
+ bw.WriteBit(true); // enable_filter_intra
+ bw.WriteBit(true); // enable_intra_edge_filter
+
+ // if ( !reduced_still_picture_header)
+ bw.WriteBit(false); // enable_interintra_compound
+ bw.WriteBit(true); // enable_masked_compound
+ bw.WriteBit(true); // enable_warped_motion
+ bw.WriteBit(false); // enable_dual_filter
+
+ bw.WriteBit(true); // enable_order_hint
+ // if ( enable_order_hint )
+ bw.WriteBit(false); // enable_jnt_comp
+ bw.WriteBit(true); // enable_ref_frame_mvs
+ // end if ( enable_order_hint )
+
+ bw.WriteBit(true); // seq_choose_screen_content_tools
+ // if ( seq_choose_screen_content_tools )
+ // seq_force_screen_content_tools = SELECT_SCREEN_CONTENT_TOOLS (2)
+ // else
+ // seq_force_screen_content_tools = f(1)
+
+ // if ( seq_force_screen_content_tools > 0 )
+ bw.WriteBit(true); // seq_choose_integer_mv
+ // if ( !seq_choose_integer_mv ) {...}
+ // end if ( seq_force_screen_content_tools > 0 )
+
+ // if ( enable_order_hint )
+ bw.WriteBits(6, 3); // order_hint_bits_minus_1
+ // end if ( enable_order_hint )
+ // end if ( !reduced_still_picture_header )
+
+ bw.WriteBit(false); // enable_superres
+ bw.WriteBit(false); // enable_cdef
+ bw.WriteBit(true); // enable_restoration
+
+ // Begin color_config( )
+ // https://aomediacodec.github.io/av1-spec/#color-config-syntax
+ bool highBitDepth = aInfo.mBitDepth >= 10;
+ bw.WriteBit(highBitDepth);
+
+ if (aInfo.mBitDepth == 12 && aInfo.mProfile != 2) {
+ NS_WARNING("Profile must be 2 for 12-bit");
+ return nullptr;
+ }
+ if (aInfo.mProfile == 2 && highBitDepth) {
+ bw.WriteBit(aInfo.mBitDepth == 12); // twelve_bit
+ }
+
+ if (aInfo.mMonochrome && aInfo.mProfile == 1) {
+ NS_WARNING("Profile 1 does not support monochrome");
+ return nullptr;
+ }
+ if (aInfo.mProfile != 1) {
+ bw.WriteBit(aInfo.mMonochrome);
+ }
+
+ const VideoColorSpace colors = aInfo.mColorSpace;
+ bool colorsPresent =
+ colors.mPrimaries != ColourPrimaries::CP_UNSPECIFIED ||
+ colors.mTransfer != TransferCharacteristics::TC_UNSPECIFIED ||
+ colors.mMatrix != MatrixCoefficients::MC_UNSPECIFIED;
+ bw.WriteBit(colorsPresent);
+
+ if (colorsPresent) {
+ bw.WriteBits(static_cast<uint8_t>(colors.mPrimaries), 8);
+ bw.WriteBits(static_cast<uint8_t>(colors.mTransfer), 8);
+ bw.WriteBits(static_cast<uint8_t>(colors.mMatrix), 8);
+ }
+
+ if (aInfo.mMonochrome) {
+ if (!aInfo.mSubsamplingX || !aInfo.mSubsamplingY) {
+ NS_WARNING("Monochrome requires 4:0:0 subsampling");
+ return nullptr;
+ }
+ if (aInfo.mChromaSamplePosition != ChromaSamplePosition::Unknown) {
+ NS_WARNING(
+ "Cannot specify chroma sample position on monochrome sequence");
+ return nullptr;
+ }
+ bw.WriteBit(colors.mRange == ColorRange::FULL);
+ } else if (colors.mPrimaries == ColourPrimaries::CP_BT709 &&
+ colors.mTransfer == TransferCharacteristics::TC_SRGB &&
+ colors.mMatrix == MatrixCoefficients::MC_IDENTITY) {
+ if (aInfo.mSubsamplingX || aInfo.mSubsamplingY ||
+ colors.mRange != ColorRange::FULL ||
+ aInfo.mChromaSamplePosition != ChromaSamplePosition::Unknown) {
+ NS_WARNING("sRGB requires 4:4:4 subsampling with full color range");
+ return nullptr;
+ }
+ } else {
+ bw.WriteBit(colors.mRange == ColorRange::FULL);
+ switch (aInfo.mProfile) {
+ case 0:
+ if (!aInfo.mSubsamplingX || !aInfo.mSubsamplingY) {
+ NS_WARNING("Main Profile requires 4:2:0 subsampling");
+ return nullptr;
+ }
+ break;
+ case 1:
+ if (aInfo.mSubsamplingX || aInfo.mSubsamplingY) {
+ NS_WARNING("High Profile requires 4:4:4 subsampling");
+ return nullptr;
+ }
+ break;
+ case 2:
+ if (aInfo.mBitDepth == 12) {
+ bw.WriteBit(aInfo.mSubsamplingX);
+ if (aInfo.mSubsamplingX) {
+ bw.WriteBit(aInfo.mSubsamplingY);
+ }
+ } else {
+ if (!aInfo.mSubsamplingX || aInfo.mSubsamplingY) {
+ NS_WARNING(
+ "Professional Profile < 12-bit requires 4:2:2 subsampling");
+ return nullptr;
+ }
+ }
+ break;
+ }
+
+ if (aInfo.mSubsamplingX && aInfo.mSubsamplingY) {
+ bw.WriteBits(static_cast<uint8_t>(aInfo.mChromaSamplePosition), 2);
+ } else {
+ if (aInfo.mChromaSamplePosition != ChromaSamplePosition::Unknown) {
+ NS_WARNING("Only 4:2:0 subsampling can specify chroma position");
+ return nullptr;
+ }
+ }
+ }
+
+ bw.WriteBit(false); // separate_uv_delta_q
+ // end color_config( )
+
+ bw.WriteBit(true); // film_grain_params_present
+
+ // trailing_bits( )
+ // https://aomediacodec.github.io/av1-spec/#trailing-bits-syntax
+ size_t numTrailingBits = 8 - (bw.BitCount() % 8);
+ bw.WriteBit(true);
+ bw.WriteBits(0, numTrailingBits - 1);
+ ASSERT_BYTE_ALIGNED(bw);
+
+ Span<const uint8_t> seqHdr(seqHdrBuffer->Elements(), seqHdrBuffer->Length());
+ aResult = NS_OK;
+ return CreateOBU(OBUType::SequenceHeader, seqHdr);
+}
+
+/* static */
+void AOMDecoder::TryReadAV1CBox(const MediaByteBuffer* aBox,
+ AV1SequenceInfo& aDestInfo,
+ MediaResult& aSeqHdrResult) {
+ // See av1C specification:
+ // https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-section
+ BitReader br(aBox);
+
+ br.ReadBits(8); // marker, version
+
+ aDestInfo.mProfile = br.ReadBits(3);
+
+ OperatingPoint op;
+ op.mLevel = br.ReadBits(5);
+ op.mTier = br.ReadBits(1);
+ aDestInfo.mOperatingPoints.AppendElement(op);
+
+ bool highBitDepth = br.ReadBit();
+ bool twelveBit = br.ReadBit();
+ aDestInfo.mBitDepth = highBitDepth ? twelveBit ? 12 : 10 : 8;
+
+ aDestInfo.mMonochrome = br.ReadBit();
+ aDestInfo.mSubsamplingX = br.ReadBit();
+ aDestInfo.mSubsamplingY = br.ReadBit();
+ aDestInfo.mChromaSamplePosition =
+ static_cast<ChromaSamplePosition>(br.ReadBits(2));
+
+ br.ReadBits(3); // reserved
+ br.ReadBit(); // initial_presentation_delay_present
+ br.ReadBits(4); // initial_presentation_delay_minus_one or reserved
+
+ ASSERT_BYTE_ALIGNED(br);
+
+ size_t skipBytes = br.BitCount() / 8;
+ Span<const uint8_t> obus(aBox->Elements() + skipBytes,
+ aBox->Length() - skipBytes);
+
+ // Minimum possible OBU header size
+ if (obus.Length() < 1) {
+ aSeqHdrResult = NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA;
+ return;
+ }
+
+ // If present, the sequence header will be redundant to some values, but any
+ // values stored in it should be treated as more accurate than av1C.
+ aSeqHdrResult = ReadSequenceHeaderInfo(obus, aDestInfo);
+}
+
+/* static */
+void AOMDecoder::WriteAV1CBox(const AV1SequenceInfo& aInfo,
+ MediaByteBuffer* aDestBox, bool& aHasSeqHdr) {
+ aHasSeqHdr = false;
+
+ BitWriter bw(aDestBox);
+
+ bw.WriteBit(true); // marker
+ bw.WriteBits(1, 7); // version
+
+ bw.WriteBits(aInfo.mProfile, 3);
+
+ MOZ_DIAGNOSTIC_ASSERT(aInfo.mOperatingPoints.Length() > 0);
+ bw.WriteBits(aInfo.mOperatingPoints[0].mLevel, 5);
+ bw.WriteBits(aInfo.mOperatingPoints[0].mTier, 1);
+
+ bw.WriteBit(aInfo.mBitDepth >= 10); // high_bitdepth
+ bw.WriteBit(aInfo.mBitDepth == 12); // twelve_bit
+
+ bw.WriteBit(aInfo.mMonochrome);
+ bw.WriteBit(aInfo.mSubsamplingX);
+ bw.WriteBit(aInfo.mSubsamplingY);
+ bw.WriteBits(static_cast<uint8_t>(aInfo.mChromaSamplePosition), 2);
+
+ bw.WriteBits(0, 3); // reserved
+ bw.WriteBit(false); // initial_presentation_delay_present
+ bw.WriteBits(0, 4); // initial_presentation_delay_minus_one or reserved
+
+ ASSERT_BYTE_ALIGNED(bw);
+
+ nsresult rv;
+ RefPtr<MediaByteBuffer> seqHdrBuffer = CreateSequenceHeader(aInfo, rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ aDestBox->AppendElements(seqHdrBuffer->Elements(), seqHdrBuffer->Length());
+ aHasSeqHdr = true;
+ }
+}
+
+/* static */
+Maybe<AOMDecoder::AV1SequenceInfo> AOMDecoder::CreateSequenceInfoFromCodecs(
+ const nsAString& aCodec) {
+ AV1SequenceInfo info;
+ OperatingPoint op;
+ uint8_t chromaSamplePosition;
+ if (!ExtractAV1CodecDetails(aCodec, info.mProfile, op.mLevel, op.mTier,
+ info.mBitDepth, info.mMonochrome,
+ info.mSubsamplingX, info.mSubsamplingY,
+ chromaSamplePosition, info.mColorSpace)) {
+ return Nothing();
+ }
+ info.mOperatingPoints.AppendElement(op);
+ info.mChromaSamplePosition =
+ static_cast<ChromaSamplePosition>(chromaSamplePosition);
+ return Some(info);
+}
+
+/* static */
+bool AOMDecoder::SetVideoInfo(VideoInfo* aDestInfo, const nsAString& aCodec) {
+ Maybe<AV1SequenceInfo> info = CreateSequenceInfoFromCodecs(aCodec);
+ if (info.isNothing()) {
+ return false;
+ }
+
+ if (!aDestInfo->mImage.IsEmpty()) {
+ info->mImage = aDestInfo->mImage;
+ }
+
+ RefPtr<MediaByteBuffer> extraData = new MediaByteBuffer();
+ bool hasSeqHdr;
+ WriteAV1CBox(info.value(), extraData, hasSeqHdr);
+ aDestInfo->mExtraData = extraData;
+ return true;
+}
+
+} // namespace mozilla
+#undef LOG
+#undef ASSERT_BYTE_ALIGNED