Commit 16fd0811 authored by Dale Curtis's avatar Dale Curtis Committed by Commit Bot

Add support for monochrome AV1 videos to Dav1dVideoDecoder.

Chrome doesn't have monochrome pixel format support, since we expect
this usage to be rare, just create fake UV planes for these video
types.

Also adds test data and hashing for a couple of existing tests which
was disabled at launch.

Fixed: 1083840
Test: New unittests. Manual inspection.
Change-Id: I3b30d4da30abc4c39bbdc1f5f611ca56b229d710
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2208012
Auto-Submit: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: default avatarThomas Guilbert <tguilbert@chromium.org>
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#769915}
parent f8f56a44
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/bits.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
...@@ -52,6 +53,9 @@ static void GetDecoderThreadCounts(const int coded_height, ...@@ -52,6 +53,9 @@ static void GetDecoderThreadCounts(const int coded_height,
static VideoPixelFormat Dav1dImgFmtToVideoPixelFormat( static VideoPixelFormat Dav1dImgFmtToVideoPixelFormat(
const Dav1dPictureParameters* pic) { const Dav1dPictureParameters* pic) {
switch (pic->layout) { switch (pic->layout) {
// Single plane monochrome images will be converted to standard 3 plane ones
// since Chromium doesn't support single Y plane images.
case DAV1D_PIXEL_LAYOUT_I400:
case DAV1D_PIXEL_LAYOUT_I420: case DAV1D_PIXEL_LAYOUT_I420:
switch (pic->bpc) { switch (pic->bpc) {
case 8: case 8:
...@@ -88,9 +92,6 @@ static VideoPixelFormat Dav1dImgFmtToVideoPixelFormat( ...@@ -88,9 +92,6 @@ static VideoPixelFormat Dav1dImgFmtToVideoPixelFormat(
DLOG(ERROR) << "Unsupported bit depth: " << pic->bpc; DLOG(ERROR) << "Unsupported bit depth: " << pic->bpc;
return PIXEL_FORMAT_UNKNOWN; return PIXEL_FORMAT_UNKNOWN;
} }
default:
DLOG(ERROR) << "Unsupported pixel format: " << pic->layout;
return PIXEL_FORMAT_UNKNOWN;
} }
} }
...@@ -348,7 +349,7 @@ bool Dav1dVideoDecoder::DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) { ...@@ -348,7 +349,7 @@ bool Dav1dVideoDecoder::DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) {
continue; continue;
} }
auto frame = CopyImageToVideoFrame(p.get()); auto frame = BindImageToVideoFrame(p.get());
if (!frame) { if (!frame) {
MEDIA_LOG(DEBUG, media_log_) MEDIA_LOG(DEBUG, media_log_)
<< "Failed to produce video frame from Dav1dPicture."; << "Failed to produce video frame from Dav1dPicture.";
...@@ -357,7 +358,7 @@ bool Dav1dVideoDecoder::DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) { ...@@ -357,7 +358,7 @@ bool Dav1dVideoDecoder::DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) {
// AV1 color space defines match ISO 23001-8:2016 via ISO/IEC 23091-4/ITU-T // AV1 color space defines match ISO 23001-8:2016 via ISO/IEC 23091-4/ITU-T
// H.273. https://aomediacodec.github.io/av1-spec/#color-config-semantics // H.273. https://aomediacodec.github.io/av1-spec/#color-config-semantics
media::VideoColorSpace color_space( VideoColorSpace color_space(
p->seq_hdr->pri, p->seq_hdr->trc, p->seq_hdr->mtrx, p->seq_hdr->pri, p->seq_hdr->trc, p->seq_hdr->mtrx,
p->seq_hdr->color_range ? gfx::ColorSpace::RangeID::FULL p->seq_hdr->color_range ? gfx::ColorSpace::RangeID::FULL
: gfx::ColorSpace::RangeID::LIMITED); : gfx::ColorSpace::RangeID::LIMITED);
...@@ -368,9 +369,11 @@ bool Dav1dVideoDecoder::DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) { ...@@ -368,9 +369,11 @@ bool Dav1dVideoDecoder::DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) {
frame->set_color_space(color_space.ToGfxColorSpace()); frame->set_color_space(color_space.ToGfxColorSpace());
frame->metadata()->SetBoolean(VideoFrameMetadata::POWER_EFFICIENT, false); frame->metadata()->SetBoolean(VideoFrameMetadata::POWER_EFFICIENT, false);
// When we use bind mode, our image data is dependent on the Dav1dPicture,
// so we must ensure it stays alive along enough.
frame->AddDestructionObserver(base::BindOnce( frame->AddDestructionObserver(base::BindOnce(
base::DoNothing::Once<ScopedPtrDav1dPicture>(), std::move(p))); base::DoNothing::Once<ScopedPtrDav1dPicture>(), std::move(p)));
output_cb_.Run(std::move(frame)); output_cb_.Run(std::move(frame));
} }
...@@ -378,22 +381,61 @@ bool Dav1dVideoDecoder::DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) { ...@@ -378,22 +381,61 @@ bool Dav1dVideoDecoder::DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) {
return true; return true;
} }
scoped_refptr<VideoFrame> Dav1dVideoDecoder::CopyImageToVideoFrame( scoped_refptr<VideoFrame> Dav1dVideoDecoder::BindImageToVideoFrame(
const Dav1dPicture* pic) { const Dav1dPicture* pic) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const gfx::Size visible_size(pic->p.w, pic->p.h);
VideoPixelFormat pixel_format = Dav1dImgFmtToVideoPixelFormat(&pic->p); VideoPixelFormat pixel_format = Dav1dImgFmtToVideoPixelFormat(&pic->p);
if (pixel_format == PIXEL_FORMAT_UNKNOWN) if (pixel_format == PIXEL_FORMAT_UNKNOWN)
return nullptr; return nullptr;
// Since we're making a copy, only copy the visible area. auto uv_plane_stride = pic->stride[1];
const gfx::Size visible_size(pic->p.w, pic->p.h); auto* u_plane = static_cast<uint8_t*>(pic->data[1]);
return VideoFrame::WrapExternalYuvData( auto* v_plane = static_cast<uint8_t*>(pic->data[2]);
const bool needs_fake_uv_planes = pic->p.layout == DAV1D_PIXEL_LAYOUT_I400;
if (needs_fake_uv_planes) {
// UV planes are half the size of the Y plane.
uv_plane_stride = base::bits::Align(pic->stride[0] / 2, 2);
const size_t size_needed = uv_plane_stride * pic->p.h;
if (!fake_uv_data_ || fake_uv_data_->size() != size_needed) {
if (pic->p.bpc == 8) {
// Avoid having base::RefCountedBytes zero initialize the memory just to
// fill it with a different value.
constexpr uint8_t kBlankUV = 256 / 2;
std::vector<unsigned char> empty_data(size_needed, kBlankUV);
// When we resize, existing frames will keep their refs on the old data.
fake_uv_data_ = base::RefCountedBytes::TakeVector(&empty_data);
} else if (pic->p.bpc == 10 || pic->p.bpc == 12) {
const uint16_t kBlankUV = (1 << pic->p.bpc) / 2;
fake_uv_data_ =
base::MakeRefCounted<base::RefCountedBytes>(size_needed);
uint16_t* data = fake_uv_data_->front_as<uint16_t>();
std::fill(data, data + size_needed / 2, kBlankUV);
}
}
u_plane = v_plane = fake_uv_data_->front_as<uint8_t>();
}
auto frame = VideoFrame::WrapExternalYuvData(
pixel_format, visible_size, gfx::Rect(visible_size), pixel_format, visible_size, gfx::Rect(visible_size),
config_.natural_size(), pic->stride[0], pic->stride[1], pic->stride[1], config_.natural_size(), pic->stride[0], uv_plane_stride, uv_plane_stride,
static_cast<uint8_t*>(pic->data[0]), static_cast<uint8_t*>(pic->data[1]), static_cast<uint8_t*>(pic->data[0]), u_plane, v_plane,
static_cast<uint8_t*>(pic->data[2]),
base::TimeDelta::FromMicroseconds(pic->m.timestamp)); base::TimeDelta::FromMicroseconds(pic->m.timestamp));
// Each frame needs a ref on the fake UV data to keep it alive until done.
if (needs_fake_uv_planes) {
frame->AddDestructionObserver(base::BindOnce(
base::DoNothing::Once<scoped_refptr<base::RefCountedBytes>>(),
fake_uv_data_));
}
return frame;
} }
} // namespace media } // namespace media
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/callback_forward.h" #include "base/callback_forward.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/threading/thread_checker.h" #include "base/threading/thread_checker.h"
#include "media/base/video_decoder.h" #include "media/base/video_decoder.h"
#include "media/base/video_decoder_config.h" #include "media/base/video_decoder_config.h"
...@@ -56,7 +57,7 @@ class MEDIA_EXPORT Dav1dVideoDecoder : public OffloadableVideoDecoder { ...@@ -56,7 +57,7 @@ class MEDIA_EXPORT Dav1dVideoDecoder : public OffloadableVideoDecoder {
// Invokes the decoder and calls |output_cb_| for any returned frames. // Invokes the decoder and calls |output_cb_| for any returned frames.
bool DecodeBuffer(scoped_refptr<DecoderBuffer> buffer); bool DecodeBuffer(scoped_refptr<DecoderBuffer> buffer);
scoped_refptr<VideoFrame> CopyImageToVideoFrame(const Dav1dPicture* img); scoped_refptr<VideoFrame> BindImageToVideoFrame(const Dav1dPicture* img);
// Used to report error messages to the client. // Used to report error messages to the client.
MediaLog* const media_log_ = nullptr; MediaLog* const media_log_ = nullptr;
...@@ -67,6 +68,10 @@ class MEDIA_EXPORT Dav1dVideoDecoder : public OffloadableVideoDecoder { ...@@ -67,6 +68,10 @@ class MEDIA_EXPORT Dav1dVideoDecoder : public OffloadableVideoDecoder {
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
// "Zero" filled UV data for monochrome images to use since Chromium doesn't
// have support for I400P(8|10|12) images.
scoped_refptr<base::RefCountedBytes> fake_uv_data_;
// Current decoder state. Used to ensure methods are called as expected. // Current decoder state. Used to ensure methods are called as expected.
DecoderState state_ = DecoderState::kUninitialized; DecoderState state_ = DecoderState::kUninitialized;
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/hash/md5.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/test/task_environment.h" #include "base/test/task_environment.h"
#include "build/build_config.h" #include "build/build_config.h"
...@@ -173,6 +174,15 @@ class Dav1dVideoDecoderTest : public testing::Test { ...@@ -173,6 +174,15 @@ class Dav1dVideoDecoderTest : public testing::Test {
output_frames_.push_back(std::move(frame)); output_frames_.push_back(std::move(frame));
} }
std::string GetVideoFrameHash(const VideoFrame& frame) {
base::MD5Context md5_context;
base::MD5Init(&md5_context);
VideoFrame::HashFrameForTesting(&md5_context, frame);
base::MD5Digest digest;
base::MD5Final(&digest, &md5_context);
return base::MD5DigestToBase16(digest);
}
MOCK_METHOD1(DecodeDone, void(DecodeStatus)); MOCK_METHOD1(DecodeDone, void(DecodeStatus));
testing::StrictMock<MockMediaLog> media_log_; testing::StrictMock<MockMediaLog> media_log_;
...@@ -215,13 +225,52 @@ TEST_F(Dav1dVideoDecoderTest, DecodeFrame_Normal) { ...@@ -215,13 +225,52 @@ TEST_F(Dav1dVideoDecoderTest, DecodeFrame_Normal) {
// Simulate decoding a single frame. // Simulate decoding a single frame.
EXPECT_EQ(DecodeStatus::OK, DecodeSingleFrame(i_frame_buffer_)); EXPECT_EQ(DecodeStatus::OK, DecodeSingleFrame(i_frame_buffer_));
ASSERT_EQ(1U, output_frames_.size()); ASSERT_EQ(1U, output_frames_.size());
const auto& frame = output_frames_.front();
EXPECT_EQ(PIXEL_FORMAT_I420, frame->format());
EXPECT_EQ("589dc641b7742ffe7a2b0d4c16aa3e86", GetVideoFrameHash(*frame));
}
TEST_F(Dav1dVideoDecoderTest, DecodeFrame_8bitMono) {
Initialize();
EXPECT_EQ(DecodeStatus::OK, DecodeSingleFrame(ReadTestDataFile(
"av1-monochrome-I-frame-320x240-8bpp")));
ASSERT_EQ(1U, output_frames_.size());
const auto& frame = output_frames_.front();
EXPECT_EQ(PIXEL_FORMAT_I420, frame->format());
EXPECT_EQ(frame->data(VideoFrame::kUPlane), frame->data(VideoFrame::kVPlane));
EXPECT_EQ("eeba03dcc9c22c4632bf74b481db36b2", GetVideoFrameHash(*frame));
}
TEST_F(Dav1dVideoDecoderTest, DecodeFrame_10bitMono) {
Initialize();
EXPECT_EQ(DecodeStatus::OK, DecodeSingleFrame(ReadTestDataFile(
"av1-monochrome-I-frame-320x240-10bpp")));
ASSERT_EQ(1U, output_frames_.size());
const auto& frame = output_frames_.front();
EXPECT_EQ(PIXEL_FORMAT_YUV420P10, frame->format());
EXPECT_EQ(frame->data(VideoFrame::kUPlane), frame->data(VideoFrame::kVPlane));
EXPECT_EQ("026c1fed9e161f09d816ac7278458a80", GetVideoFrameHash(*frame));
}
TEST_F(Dav1dVideoDecoderTest, DecodeFrame_12bitMono) {
Initialize();
EXPECT_EQ(DecodeStatus::OK, DecodeSingleFrame(ReadTestDataFile(
"av1-monochrome-I-frame-320x240-12bpp")));
ASSERT_EQ(1U, output_frames_.size());
const auto& frame = output_frames_.front();
EXPECT_EQ(PIXEL_FORMAT_YUV420P12, frame->format());
EXPECT_EQ(frame->data(VideoFrame::kUPlane), frame->data(VideoFrame::kVPlane));
EXPECT_EQ("32115092dc00fbe86823b0b714a0f63e", GetVideoFrameHash(*frame));
} }
// Decode |i_frame_buffer_| and then a frame with a larger width and verify // Decode |i_frame_buffer_| and then a frame with a larger width and verify
// the output size was adjusted. // the output size was adjusted.
// TODO(dalecurtis): Get an I-frame from a larger video. TEST_F(Dav1dVideoDecoderTest, DecodeFrame_LargerWidth) {
TEST_F(Dav1dVideoDecoderTest, DISABLED_DecodeFrame_LargerWidth) { DecodeIFrameThenTestFile("av1-I-frame-1280x720", gfx::Size(1280, 720));
DecodeIFrameThenTestFile("av1-I-frame-320x240", gfx::Size(1280, 720));
} }
// Decode a VP9 frame which should trigger a decoder error. // Decode a VP9 frame which should trigger a decoder error.
......
...@@ -504,6 +504,17 @@ using key ID [1] and key [2]. ...@@ -504,6 +504,17 @@ using key ID [1] and key [2].
Unless noted otherwise, the codec string is `av01.0.04M.08` for 8-bit files, Unless noted otherwise, the codec string is `av01.0.04M.08` for 8-bit files,
and `av01.0.04M.10` for 10-bit files. and `av01.0.04M.10` for 10-bit files.
#### av1-I-frame-320x240
vpxdec media/test/data/bear-vp9.webm -o bear.y4m
aomenc -o bear.ivf -p 2 --target-bitrate=150 bear.y4m --limit=1 --ivf
tail -c +45 bear.ivf > av1-I-frame-320x240
#### av1-I-frame-1280x720
Same as av1-I-frame-320x240 but using bear-1280x720.webm as input.
#### av1-monochrome-I-frame-320x240-[8,10,12]bpp
Same as av1-I-frame-320x240 with --monochrome and -b=[8,10,12] aomenc options.
#### bear-av1-cenc.mp4 #### bear-av1-cenc.mp4
Encrypted version of bear-av1.mp4. Encrypted by [Shaka Packager] built locally Encrypted version of bear-av1.mp4. Encrypted by [Shaka Packager] built locally
at commit 53aa775ea488c0ffd3a2e1cb78ad000154e414e1 using key ID [1] and key [2]. at commit 53aa775ea488c0ffd3a2e1cb78ad000154e414e1 using key ID [1] and key [2].
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment