0

Extract AGTM metadata in AV1Decoder.

Bug: 395659818
Change-Id: I488342bc0cd46d756e15bc35f13a559ff1f7bd20
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6352839
Reviewed-by: ccameron chromium <ccameron@chromium.org>
Commit-Queue: Maryla Ustarroz-Calonge <maryla@google.com>
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1432828}
This commit is contained in:
Maryla
2025-03-14 10:16:12 -07:00
committed by Chromium LUCI CQ
parent 916fde64ac
commit 02acf581b8
12 changed files with 181 additions and 29 deletions

@ -44,6 +44,8 @@ source_set("base") {
}
sources = [
"agtm.cc",
"agtm.h",
"amplitude_peak_detector.cc",
"amplitude_peak_detector.h",
"android_overlay_config.cc",
@ -579,6 +581,7 @@ static_library("test_support") {
source_set("unit_tests") {
testonly = true
sources = [
"agtm_unittest.cc",
"amplitude_peak_detector_unittest.cc",
"audio_block_fifo_unittest.cc",
"audio_buffer_converter_unittest.cc",

37
media/base/agtm.cc Normal file

@ -0,0 +1,37 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/base/agtm.h"
#include "third_party/skia/include/core/SkData.h"
namespace media {
// Returns AGTM metadata if the ITU-T T.35 message contains some.
std::optional<gfx::HdrMetadataAgtm> GetHdrMetadataAgtmFromItutT35(
uint8_t t35_country_code,
base::span<const uint8_t> t35_payload) {
// itu_t_t35_terminal_provider_code (2 bytes)
// + itu_t_t35_terminal_provider_oriented_code (2 bytes)
// + application_identifier (1 byte) + application_version (1 byte)
static constexpr size_t kItuT35HeaderSize = 6;
const bool isAgtm = t35_country_code == 0xB5 /* United States */ &&
t35_payload.size() >= kItuT35HeaderSize &&
t35_payload[0] == 0x58 /* placeholder (AOM) */ &&
t35_payload[1] == 0x90 /* placeholder (AOM) */ &&
t35_payload[2] == 0x69 /* placeholder (AGTM) */ &&
t35_payload[3] == 0x42 /* placeholder (AGTM) */ &&
t35_payload[4] == 0x05 /* app identifier */ &&
t35_payload[5] == 0x00 /* app version */;
if (!isAgtm) {
return std::nullopt;
}
const auto agtm_payload_span = t35_payload.subspan(kItuT35HeaderSize);
sk_sp<SkData> agtm_skdata =
SkData::MakeWithCopy(agtm_payload_span.data(), agtm_payload_span.size());
return gfx::HdrMetadataAgtm(std::move(agtm_skdata));
}
} // namespace media

23
media/base/agtm.h Normal file

@ -0,0 +1,23 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_BASE_AGTM_H_
#define MEDIA_BASE_AGTM_H_
#include <optional>
#include "base/containers/span.h"
#include "media/base/media_export.h"
#include "ui/gfx/hdr_metadata.h"
namespace media {
// Returns AGTM metadata if the ITU-T T.35 message contains some.
MEDIA_EXPORT std::optional<gfx::HdrMetadataAgtm> GetHdrMetadataAgtmFromItutT35(
uint8_t t35_country_code,
base::span<const uint8_t> t35_payload);
} // namespace media
#endif // MEDIA_BASE_AGTM_H_

@ -0,0 +1,42 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/base/agtm.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkData.h"
namespace media {
TEST(GetHdrMetadataAgtmFromItutT35, HasMetadata) {
const uint8_t t35_country_code = 0xB5;
const std::vector<uint8_t> data = {0x58, 0x90, 0x69, 0x42, 0x05,
0x00, 0x01, 0x02, 0x03};
const std::optional<gfx::HdrMetadataAgtm> agtm =
GetHdrMetadataAgtmFromItutT35(t35_country_code, data);
ASSERT_TRUE(agtm.has_value());
EXPECT_EQ(agtm->payload->size(), 3u);
const std::vector<uint8_t> expected = {0x01, 0x02, 0x03};
EXPECT_TRUE(agtm->payload->equals(
SkData::MakeWithCopy(expected.data(), expected.size()).get()));
}
TEST(GetHdrMetadataAgtmFromItutT35, WrongType) {
const uint8_t t35_country_code = 0xB5;
const std::vector<uint8_t> data = {
0x58, 0x90, 0x69, 0xff /* wrong value*/, 0x05, 0x00, 0x01, 0x02, 0x03};
const std::optional<gfx::HdrMetadataAgtm> agtm =
GetHdrMetadataAgtmFromItutT35(t35_country_code, data);
ASSERT_FALSE(agtm.has_value());
}
TEST(GetHdrMetadataAgtmFromItutT35, DataTooShort) {
const uint8_t t35_country_code = 0xB5;
const std::vector<uint8_t> data = {0x58, 0x90, 0x69};
const std::optional<gfx::HdrMetadataAgtm> agtm =
GetHdrMetadataAgtmFromItutT35(t35_country_code, data);
ASSERT_FALSE(agtm.has_value());
}
} // namespace media

@ -16,6 +16,7 @@
#include "base/strings/stringprintf.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "media/base/agtm.h"
#include "media/base/decoder_buffer.h"
#include "media/base/limits.h"
#include "media/base/media_log.h"
@ -115,32 +116,6 @@ static void LogDav1dMessage(void* cookie, const char* format, va_list ap) {
DLOG(ERROR) << log;
}
// Returns AGTM metadata if the ITU-T T.35 message contains some.
static std::optional<gfx::HdrMetadataAgtm> GetHdrMetadataAgtm(
uint8_t t35_country_code,
base::span<const uint8_t> t35_payload) {
// itu_t_t35_terminal_provider_code (2 bytes)
// + itu_t_t35_terminal_provider_oriented_code (2 bytes)
// + application_identifier (1 byte) + application_version (1 byte)
static constexpr size_t kItuT35HeaderSize = 6;
const bool isAgtm = t35_country_code == 0xB5 /* United States */ &&
t35_payload.size() >= kItuT35HeaderSize &&
t35_payload[0] == 0x58 /* placeholder (AOM) */ &&
t35_payload[1] == 0x90 /* placeholder (AOM) */ &&
t35_payload[2] == 0x69 /* placeholder (AGTM) */ &&
t35_payload[3] == 0x42 /* placeholder (AGTM) */ &&
t35_payload[4] == 0x05 /* app identifier */ &&
t35_payload[5] == 0x00 /* app version */;
if (!isAgtm) {
return std::nullopt;
}
const auto agtm_payload_span = t35_payload.subspan(kItuT35HeaderSize);
sk_sp<SkData> agtm_skdata =
SkData::MakeWithCopy(agtm_payload_span.data(), agtm_payload_span.size());
return gfx::HdrMetadataAgtm(std::move(agtm_skdata));
}
// Dynamically allocated Dav1dPicture opaque data.
struct FrameBufferData {
FrameBufferData(void* fb, scoped_refptr<FrameBufferPool> pool)
@ -502,7 +477,8 @@ bool Dav1dVideoDecoder::DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) {
auto t35_payload_span = UNSAFE_BUFFERS(base::span<const uint8_t>(
p->itut_t35->payload, p->itut_t35->payload_size));
const std::optional<gfx::HdrMetadataAgtm> agtm =
GetHdrMetadataAgtm(p->itut_t35->country_code, t35_payload_span);
GetHdrMetadataAgtmFromItutT35(p->itut_t35->country_code,
t35_payload_span);
if (agtm.has_value()) {
gfx::HDRMetadata hdr_metadata =
config_.hdr_metadata().value_or(gfx::HDRMetadata());

@ -7,6 +7,8 @@
#pragma allow_unsafe_libc_calls
#endif
#include "media/filters/dav1d_video_decoder.h"
#include <memory>
#include <string>
#include <utility>
@ -25,9 +27,9 @@
#include "media/base/test_helpers.h"
#include "media/base/video_frame.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/dav1d_video_decoder.h"
#include "media/filters/in_memory_url_protocol.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/skia/include/core/SkData.h"
using ::testing::_;
@ -285,7 +287,8 @@ TEST_F(Dav1dVideoDecoderTest, DecodeFrame_AgtmMetadata) {
const auto& frame = output_frames_.front();
ASSERT_TRUE(frame->hdr_metadata().has_value());
EXPECT_TRUE(frame->hdr_metadata()->agtm.has_value());
ASSERT_TRUE(frame->hdr_metadata()->agtm.has_value());
EXPECT_EQ(frame->hdr_metadata()->agtm->payload->size(), 99u);
}
// Decode |i_frame_buffer_| and then a frame with a larger width and verify

@ -15,6 +15,7 @@
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "media/base/agtm.h"
#include "media/base/limits.h"
#include "media/base/media_switches.h"
#include "media/gpu/av1_picture.h"
@ -23,6 +24,7 @@
#include "third_party/libgav1/src/src/gav1/status_code.h"
#include "third_party/libgav1/src/src/utils/common.h"
#include "third_party/libgav1/src/src/utils/constants.h"
#include "third_party/skia/include/core/SkData.h"
#include "ui/gfx/hdr_metadata.h"
namespace media {
@ -481,6 +483,22 @@ AcceleratedVideoDecoder::DecodeResult AV1Decoder::DecodeInternal() {
hdr_metadata_->smpte_st_2086 =
ToGfxSmpteSt2086(current_frame_->hdr_mdcv());
}
if (current_frame_->itut_t35_set()) {
// SAFETY: The best we can do is trust the size provided by libgav1.
auto t35_payload_span = UNSAFE_BUFFERS(base::span<const uint8_t>(
current_frame_->itut_t35().payload_bytes,
static_cast<size_t>(current_frame_->itut_t35().payload_size)));
const std::optional<gfx::HdrMetadataAgtm> agtm =
GetHdrMetadataAgtmFromItutT35(current_frame_->itut_t35().country_code,
t35_payload_span);
if (agtm.has_value()) {
if (!hdr_metadata_.has_value()) {
hdr_metadata_.emplace();
}
// Overwrite existing AGTM metadata if any.
hdr_metadata_->agtm = agtm;
}
}
DCHECK(current_sequence_header_->film_grain_params_present ||
!frame_header.film_grain_params.apply_grain);

@ -37,6 +37,7 @@
#include "third_party/libgav1/src/src/utils/common.h"
#include "third_party/libgav1/src/src/utils/constants.h"
#include "third_party/libgav1/src/src/utils/types.h"
#include "third_party/skia/include/core/SkData.h"
using ::testing::_;
using ::testing::DoAll;
@ -933,6 +934,47 @@ TEST_F(AV1DecoderTest, DecodeWithFrameSizeChange) {
testing::Not(testing::Contains(DecodeResult::kDecodeError)));
}
TEST_F(AV1DecoderTest, DecodeStreamWithAgtmMetadata) {
constexpr gfx::Size kFrameSize(320, 240);
constexpr gfx::Size kRenderSize(320, 240);
constexpr auto kProfile = libgav1::BitstreamProfile::kProfile0;
const std::string kAgtmStream("av1-I-frame-320x240-agtm.ivf");
std::vector<scoped_refptr<DecoderBuffer>> buffers = ReadIVF(kAgtmStream);
ASSERT_FALSE(buffers.empty());
std::vector<DecodeResult> expected = {DecodeResult::kConfigChange};
std::vector<DecodeResult> results;
for (auto buffer : buffers) {
::testing::InSequence sequence;
auto av1_picture = base::MakeRefCounted<AV1Picture>();
EXPECT_CALL(*mock_accelerator_, CreateAV1Picture(/*apply_grain=*/false))
.WillOnce(Return(av1_picture));
EXPECT_CALL(
*mock_accelerator_,
SubmitDecode(
MatchesFrameHeader(kFrameSize, kRenderSize,
/*show_existing_frame=*/false,
/*show_frame=*/true),
MatchesYUV420SequenceHeader(kProfile, /*bitdepth=*/8, kFrameSize,
/*film_grain_params_present=*/false),
_, NonEmptyTileBuffers(), MatchesFrameData(buffer)))
.WillOnce(Return(AV1Decoder::AV1Accelerator::Status::kOk));
EXPECT_CALL(*mock_accelerator_,
OutputPicture(SameAV1PictureInstance(av1_picture)))
.WillOnce(Return(true));
for (DecodeResult r : Decode(buffer)) {
results.push_back(r);
}
expected.push_back(DecodeResult::kRanOutOfStreamData);
testing::Mock::VerifyAndClearExpectations(mock_accelerator_);
}
EXPECT_EQ(results, expected);
const std::optional<gfx::HDRMetadata> hdr_metadata =
decoder_->GetHDRMetadata();
ASSERT_TRUE(hdr_metadata.has_value());
ASSERT_TRUE(hdr_metadata->agtm.has_value());
EXPECT_EQ(hdr_metadata->agtm->payload->size(), 99u);
}
// TODO(hiroh): Add more tests: reference frame tracking, render size change,
// profile change, bit depth change, render size different than the frame size,
// visible rectangle change in the middle of video sequence, reset while waiting

@ -850,6 +850,12 @@ Same as av1-I-frame-320x240 with --monochrome and -b=[8,10,12] aomenc options.
#### av1-I-frame-320x240-agtm
Same as av1-I-frame-320x240 but with an AGTM ITU_T35 metadata OBU added.
#### av1-I-frame-320x240-agtm.ivf
Created by converting av1-I-frame-320x240-agtm (raw OBU) to IVF:
```
ffmpeg -i av1-I-frame-320x240-agtm -c:v copy av1-I-frame-320x240-agtm.ivf
```
#### bear-av1-cenc.mp4
Encrypted version of bear-av1.mp4. Encrypted by [Shaka Packager] built locally
at commit 53aa775ea488c0ffd3a2e1cb78ad000154e414e1 using key ID [1] and key [2].

Binary file not shown.

@ -35,6 +35,7 @@ data/av1-1-b8-02-allintra.ivf.json
data/av1-I-frame-1280x720
data/av1-I-frame-320x240
data/av1-I-frame-320x240-agtm
data/av1-I-frame-320x240-agtm.ivf
data/av1-film_grain.ivf
data/av1-monochrome-I-frame-320x240-10bpp
data/av1-monochrome-I-frame-320x240-12bpp

@ -47,6 +47,7 @@
//media/test/data/av1-I-frame-1280x720
//media/test/data/av1-I-frame-320x240
//media/test/data/av1-I-frame-320x240-agtm
//media/test/data/av1-I-frame-320x240-agtm.ivf
//media/test/data/av1-film_grain.ivf
//media/test/data/av1-monochrome-I-frame-320x240-10bpp
//media/test/data/av1-monochrome-I-frame-320x240-12bpp