Commit 859da545 authored by Hirokazu Honda's avatar Hirokazu Honda Committed by Commit Bot

media/gpu: Add AcceleratedVideoDecoder for AV1

This CL adds an implementation of AcceleratedVideoDecoder for
AV1, AV1Decoder and a few necessary classes for it. They use
libgav1 parser and structures. Since libgav1 is built in
ChromeOS, AV1Decoder is built for ChromeOS x86 targets only.

Bug: 1029212, 1000988
Test: video_decode_accelerator_tests test-25fps.av1.ivf --use_vd
Test: av1_decoder_unittest
Change-Id: Id9d0be18484006005803ae409e45c2028d44c315
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2397997
Commit-Queue: Hirokazu Honda <hiroh@chromium.org>
Reviewed-by: default avatarAndres Calderon Jaramillo <andrescj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#827593}
parent 62693518
......@@ -297,6 +297,7 @@ source_set("common") {
]
}
public_deps = []
deps = [
":buildflags",
"//base",
......@@ -307,6 +308,17 @@ source_set("common") {
"//ui/gfx:memory_buffer",
"//ui/gfx/geometry",
]
if (is_ash && use_vaapi) {
assert(use_libgav1_parser)
sources += [
"av1_decoder.cc",
"av1_decoder.h",
"av1_picture.cc",
"av1_picture.h",
]
public_deps += [ "//third_party/libgav1:libgav1" ]
}
}
source_set("command_buffer_helper") {
......@@ -524,6 +536,12 @@ source_set("unit_tests") {
if (use_v4l2_codec || use_vaapi) {
sources += [ "vp8_decoder_unittest.cc" ]
}
if (is_ash && use_vaapi) {
sources += [ "av1_decoder_unittest.cc" ]
deps += [ "//third_party/ffmpeg" ]
}
if (is_win && enable_library_cdms) {
sources += [
"windows/d3d11_copying_texture_wrapper_unittest.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/av1_decoder.h"
#include <bitset>
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "media/base/limits.h"
#include "media/gpu/av1_picture.h"
#include "third_party/libgav1/src/src/decoder_state.h"
#include "third_party/libgav1/src/src/gav1/status_code.h"
#include "third_party/libgav1/src/src/utils/constants.h"
namespace media {
namespace {
// (Section 6.4.1):
//
// - "An operating point specifies which spatial and temporal layers should be
// decoded."
//
// - "The order of operating points indicates the preferred order for producing
// an output: a decoder should select the earliest operating point in the list
// that meets its decoding capabilities as expressed by the level associated
// with each operating point."
//
// For simplicity, we always select operating point 0 and will validate that it
// doesn't have scalability information.
constexpr unsigned int kDefaultOperatingPoint = 0;
// Conversion function from libgav1 profiles to media::VideoCodecProfile.
VideoCodecProfile AV1ProfileToVideoCodecProfile(
libgav1::BitstreamProfile profile) {
switch (profile) {
case libgav1::kProfile0:
return AV1PROFILE_PROFILE_MAIN;
case libgav1::kProfile1:
return AV1PROFILE_PROFILE_HIGH;
case libgav1::kProfile2:
return AV1PROFILE_PROFILE_PRO;
default:
// ObuParser::ParseSequenceHeader() validates the profile.
NOTREACHED() << "Invalid profile: " << base::strict_cast<int>(profile);
return AV1PROFILE_PROFILE_MAIN;
}
}
// Returns true iff the sequence has spatial or temporal scalability information
// for the selected operating point.
bool SequenceUsesScalability(int operating_point_idc) {
return operating_point_idc != 0;
}
bool IsYUV420Sequence(const libgav1::ColorConfig& color_config) {
return color_config.subsampling_x == 1 && color_config.subsampling_y == 1 &&
!color_config.is_monochrome;
}
} // namespace
AV1Decoder::AV1Decoder(std::unique_ptr<AV1Accelerator> accelerator,
VideoCodecProfile profile)
: buffer_pool_(std::make_unique<libgav1::BufferPool>(
/*on_frame_buffer_size_changed=*/nullptr,
/*get_frame_buffer=*/nullptr,
/*release_frame_buffer=*/nullptr,
/*callback_private_data=*/nullptr)),
state_(std::make_unique<libgav1::DecoderState>()),
accelerator_(std::move(accelerator)),
profile_(profile) {
ref_frames_.fill(nullptr);
}
AV1Decoder::~AV1Decoder() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// |buffer_pool_| checks that all the allocated frames are released in its
// dtor. Explicitly destruct |state_| before |buffer_pool_| to release frames
// in |reference_frame| in |state_|.
state_.reset();
}
bool AV1Decoder::HasNewSequenceHeader() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(parser_);
const auto& obu_headers = parser_->obu_headers();
const bool has_sequence_header =
std::find_if(obu_headers.begin(), obu_headers.end(),
[](const auto& obu_header) {
return obu_header.type == libgav1::kObuSequenceHeader;
}) != obu_headers.end();
if (!has_sequence_header)
return false;
if (!current_sequence_header_)
return true;
return parser_->sequence_header().ParametersChanged(
*current_sequence_header_);
}
bool AV1Decoder::Flush() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << "Decoder flush";
Reset();
return true;
}
void AV1Decoder::Reset() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ClearCurrentFrame();
// Resetting current sequence header might not be necessary. But this violates
// AcceleratedVideoDecoder interface spec, "Reset any current state that may
// be cached in the accelerator."
// TODO(hiroh): We may want to change this interface spec so that a caller
// doesn't have to allocate video frames buffers every seek operation.
current_sequence_header_.reset();
visible_rect_ = gfx::Rect();
frame_size_ = gfx::Size();
profile_ = VideoCodecProfile::VIDEO_CODEC_PROFILE_UNKNOWN;
stream_id_ = 0;
stream_ = nullptr;
stream_size_ = 0;
on_error_ = false;
state_ = std::make_unique<libgav1::DecoderState>();
ClearReferenceFrames();
parser_.reset();
buffer_pool_ = std::make_unique<libgav1::BufferPool>(
/*on_frame_buffer_size_changed=*/nullptr,
/*get_frame_buffer=*/nullptr,
/*release_frame_buffer=*/nullptr,
/*callback_private_data=*/nullptr);
}
void AV1Decoder::SetStream(int32_t id, const DecoderBuffer& decoder_buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
stream_id_ = id;
stream_ = decoder_buffer.data();
stream_size_ = decoder_buffer.data_size();
ClearCurrentFrame();
parser_ = base::WrapUnique(new (std::nothrow) libgav1::ObuParser(
decoder_buffer.data(), decoder_buffer.data_size(), kDefaultOperatingPoint,
buffer_pool_.get(), state_.get()));
if (!parser_) {
on_error_ = true;
return;
}
if (current_sequence_header_)
parser_->set_sequence_header(*current_sequence_header_);
}
void AV1Decoder::ClearCurrentFrame() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
current_frame_.reset();
current_frame_header_.reset();
}
AcceleratedVideoDecoder::DecodeResult AV1Decoder::Decode() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (on_error_)
return kDecodeError;
auto result = DecodeInternal();
on_error_ = result == kDecodeError;
return result;
}
AcceleratedVideoDecoder::DecodeResult AV1Decoder::DecodeInternal() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!parser_) {
DLOG(ERROR) << "Decode() is called before SetStream()";
return kDecodeError;
}
while (parser_->HasData() || current_frame_header_) {
base::ScopedClosureRunner clear_current_frame(
base::BindOnce(&AV1Decoder::ClearCurrentFrame, base::Unretained(this)));
if (!current_frame_header_) {
libgav1::StatusCode status_code = parser_->ParseOneFrame(&current_frame_);
if (status_code != libgav1::kStatusOk) {
DLOG(WARNING) << "Failed to parse OBU: "
<< libgav1::GetErrorString(status_code);
return kDecodeError;
}
if (!current_frame_) {
DLOG(WARNING) << "No frame found. Skipping the current stream";
continue;
}
current_frame_header_ = parser_->frame_header();
// Detects if a new coded video sequence is starting.
// TODO(b/171853869): Replace HasNewSequenceHeader() with whatever
// libgav1::ObuParser provides for more than one sequence headers case.
if (HasNewSequenceHeader()) {
// TODO(b/171853869): Remove this check once libgav1::ObuParser does
// this check.
if (current_frame_header_->frame_type != libgav1::kFrameKey ||
!current_frame_header_->show_frame ||
current_frame_header_->show_existing_frame ||
current_frame_->temporal_id() != 0) {
// Section 7.5.
DVLOG(1)
<< "The first frame successive to sequence header OBU must be a "
<< "keyframe with show_frame=1, show_existing_frame=0 and "
<< "temporal_id=0";
return kDecodeError;
}
if (SequenceUsesScalability(
parser_->sequence_header()
.operating_point_idc[kDefaultOperatingPoint])) {
DVLOG(3) << "Either temporal or spatial layer decoding is not "
<< "supported";
return kDecodeError;
}
current_sequence_header_ = parser_->sequence_header();
// TODO(hiroh): Expose the bit depth to let the AV1Decoder client
// perform these checks.
if (current_sequence_header_->color_config.bitdepth != 8) {
// TODO(hiroh): Handle 10/12 bit depth.
DVLOG(1) << "10/12 bit depth are not supported";
return kDecodeError;
}
if (!IsYUV420Sequence(current_sequence_header_->color_config)) {
DVLOG(1) << "Only YUV 4:2:0 is supported";
return kDecodeError;
}
const gfx::Size new_frame_size(
base::strict_cast<int>(current_sequence_header_->max_frame_width),
base::strict_cast<int>(current_sequence_header_->max_frame_height));
gfx::Rect new_visible_rect(
base::strict_cast<int>(current_frame_header_->render_width),
base::strict_cast<int>(current_frame_header_->render_height));
const VideoCodecProfile new_profile =
AV1ProfileToVideoCodecProfile(current_sequence_header_->profile);
DCHECK(!new_frame_size.IsEmpty());
if (!gfx::Rect(new_frame_size).Contains(new_visible_rect)) {
DVLOG(1) << "Render size exceeds picture size. render size: "
<< new_visible_rect.ToString()
<< ", picture size: " << new_frame_size.ToString();
new_visible_rect = gfx::Rect(new_frame_size);
}
ClearReferenceFrames();
// Issues kConfigChange only if either the dimensions or profile is
// changed.
if (frame_size_ != new_frame_size ||
visible_rect_ != new_visible_rect || profile_ != new_profile) {
frame_size_ = new_frame_size;
visible_rect_ = new_visible_rect;
profile_ = new_profile;
clear_current_frame.ReplaceClosure(base::DoNothing());
return kConfigChange;
}
}
}
if (!current_sequence_header_) {
// Decoding is not doable because we haven't received a sequence header.
// This occurs when seeking a video.
DVLOG(3) << "Discarded the current frame because no sequence header has "
<< "been found yet";
continue;
}
DCHECK(current_frame_header_);
const auto& frame_header = *current_frame_header_;
if (frame_header.show_existing_frame) {
const size_t frame_to_show =
base::checked_cast<size_t>(frame_header.frame_to_show);
DCHECK_LE(0u, frame_to_show);
DCHECK_LT(frame_to_show, ref_frames_.size());
if (!CheckAndCleanUpReferenceFrames()) {
DLOG(ERROR) << "The states of reference frames are different between"
<< "|ref_frames_| and |state_->reference_frame|";
return kDecodeError;
}
auto pic = ref_frames_[frame_to_show];
CHECK(pic);
pic = pic->Duplicate();
if (!pic) {
DVLOG(1) << "Failed duplication";
return kDecodeError;
}
pic->set_bitstream_id(stream_id_);
if (!accelerator_->OutputPicture(*pic)) {
return kDecodeError;
}
// libgav1::ObuParser sets |current_frame_| to the frame to show while
// |current_frame_header_| is the frame header of the currently parsed
// frame. If |current_frame_| is a keyframe, then refresh_frame_flags must
// be 0xff. Otherwise, refresh_frame_flags must be 0x00 (Section 5.9.2).
DCHECK(current_frame_->frame_type() == libgav1::kFrameKey ||
current_frame_header_->refresh_frame_flags == 0x00);
DCHECK(current_frame_->frame_type() != libgav1::kFrameKey ||
current_frame_header_->refresh_frame_flags == 0xff);
UpdateReferenceFrames(std::move(pic));
continue;
}
if (parser_->tile_buffers().empty()) {
// The last call to ParseOneFrame() didn't actually have any tile groups.
// This could happen in rare cases (for example, if there is a Metadata
// OBU after the TileGroup OBU). Ignore this case.
continue;
}
const gfx::Size current_frame_size(
base::strict_cast<int>(frame_header.width),
base::strict_cast<int>(frame_header.height));
if (current_frame_size != frame_size_) {
// TODO(hiroh): This must be handled in decoding spatial layer.
DVLOG(1) << "Resolution change in the middle of video sequence (i.e."
<< " between sequence headers) is not supported";
return kDecodeError;
}
if (current_frame_size.width() !=
base::strict_cast<int>(frame_header.upscaled_width)) {
DVLOG(1) << "Super resolution is not supported";
return kDecodeError;
}
const gfx::Rect current_visible_rect(
base::strict_cast<int>(frame_header.render_width),
base::strict_cast<int>(frame_header.render_height));
if (current_visible_rect != visible_rect_) {
// TODO(andrescj): Handle the visible rectangle change in the middle of
// video sequence.
DVLOG(1) << "Visible rectangle change in the middle of video sequence"
<< "(i.e. between sequence headers) is not supported";
return kDecodeError;
}
auto pic = accelerator_->CreateAV1Picture(
frame_header.film_grain_params.apply_grain);
if (!pic) {
clear_current_frame.ReplaceClosure(base::DoNothing());
return kRanOutOfSurfaces;
}
pic->set_visible_rect(current_visible_rect);
pic->set_bitstream_id(stream_id_);
pic->frame_header = frame_header;
// TODO(hiroh): Set color space and decrypt config.
if (!DecodeAndOutputPicture(std::move(pic), parser_->tile_buffers()))
return kDecodeError;
}
return kRanOutOfStreamData;
}
void AV1Decoder::UpdateReferenceFrames(scoped_refptr<AV1Picture> pic) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(state_);
DCHECK(current_frame_header_);
const uint8_t refresh_frame_flags =
current_frame_header_->refresh_frame_flags;
DCHECK(current_frame_->frame_type() != libgav1::kFrameKey ||
current_frame_header_->refresh_frame_flags == 0xff);
const std::bitset<libgav1::kNumReferenceFrameTypes> update_reference_frame(
refresh_frame_flags);
for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; ++i) {
if (update_reference_frame[i])
ref_frames_[i] = pic;
}
state_->UpdateReferenceFrames(current_frame_,
base::strict_cast<int>(refresh_frame_flags));
}
void AV1Decoder::ClearReferenceFrames() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(state_);
ref_frames_.fill(nullptr);
// If AV1Decoder has decided to clear the reference frames, then ObuParser
// must have also decided to do so.
DCHECK_EQ(base::STLCount(state_->reference_frame, nullptr),
static_cast<int>(state_->reference_frame.size()));
}
bool AV1Decoder::CheckAndCleanUpReferenceFrames() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(state_);
const auto& reference_valid = state_->reference_valid;
for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; ++i) {
if (reference_valid[i] && !ref_frames_[i])
return false;
if (!reference_valid[i] && ref_frames_[i])
ref_frames_[i].reset();
}
return true;
}
bool AV1Decoder::DecodeAndOutputPicture(
scoped_refptr<AV1Picture> pic,
const libgav1::Vector<libgav1::TileBuffer>& tile_buffers) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(pic);
DCHECK(current_sequence_header_);
DCHECK(stream_);
DCHECK_GT(stream_size_, 0u);
if (!CheckAndCleanUpReferenceFrames()) {
DLOG(ERROR) << "The states of reference frames are different between"
<< "|ref_frames_| and |state_->reference_frame|";
return false;
}
if (!accelerator_->SubmitDecode(*pic, *current_sequence_header_, ref_frames_,
tile_buffers,
base::make_span(stream_, stream_size_))) {
return false;
}
if (pic->frame_header.show_frame && !accelerator_->OutputPicture(*pic))
return false;
UpdateReferenceFrames(std::move(pic));
return true;
}
gfx::Size AV1Decoder::GetPicSize() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(hiroh): It should be safer to align this by 64 or 128 (depending on
// use_128x128_superblock) so that a driver doesn't touch out of the buffer.
return frame_size_;
}
gfx::Rect AV1Decoder::GetVisibleRect() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return visible_rect_;
}
VideoCodecProfile AV1Decoder::GetProfile() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return profile_;
}
size_t AV1Decoder::GetRequiredNumOfPictures() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(hiroh): Double this value in the case of film grain sequence.
constexpr size_t kPicsInPipeline = limits::kMaxVideoFrames + 1;
return kPicsInPipeline + GetNumReferenceFrames();
}
size_t AV1Decoder::GetNumReferenceFrames() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return libgav1::kNumReferenceFrameTypes;
}
} // namespace media
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_GPU_AV1_DECODER_H_
#define MEDIA_GPU_AV1_DECODER_H_
#include <array>
#include <memory>
#include "base/containers/span.h"
#include "base/macros.h"
#include "base/sequence_checker.h"
#include "media/base/video_codecs.h"
#include "media/base/video_color_space.h"
#include "media/gpu/accelerated_video_decoder.h"
#include "media/gpu/media_gpu_export.h"
#include "third_party/libgav1/src/src/utils/constants.h"
// For libgav1::RefCountedBufferPtr.
#include "third_party/libgav1/src/src/buffer_pool.h"
// For libgav1::ObuSequenceHeader. base::Optional demands ObuSequenceHeader to
// fulfill std::is_trivially_constructible if it is forward-declared. But
// ObuSequenceHeader doesn't.
#include "third_party/libgav1/src/src/obu_parser.h"
namespace libgav1 {
struct DecoderState;
struct ObuFrameHeader;
template <typename T>
class Vector;
} // namespace libgav1
namespace media {
class AV1Picture;
using AV1ReferenceFrameVector =
std::array<scoped_refptr<AV1Picture>, libgav1::kNumReferenceFrameTypes>;
// Clients of this class are expected to pass an AV1 OBU stream and are expected
// to provide an implementation of AV1Accelerator for offloading final steps
// of the decoding process.
//
// This class must be created, called and destroyed on a single thread, and
// does nothing internally on any other thread.
class MEDIA_GPU_EXPORT AV1Decoder : public AcceleratedVideoDecoder {
public:
class MEDIA_GPU_EXPORT AV1Accelerator {
public:
AV1Accelerator() = default;
virtual ~AV1Accelerator() = default;
AV1Accelerator(const AV1Accelerator&) = delete;
AV1Accelerator& operator=(const AV1Accelerator&) = delete;
// Creates an AV1Picture that the AV1Decoder can use to store some of the
// information needed to request accelerated decoding. This picture is later
// passed when calling SubmitDecode() so that the AV1Accelerator can submit
// the decode request to the driver. It may also be stored for use as
// reference to decode other pictures.
// When a picture is no longer needed by the decoder, it will just drop
// its reference to it, and it may do so at any time.
// Note that this may return nullptr if the accelerator is not able to
// provide any new pictures at the given time. The decoder must handle this
// case and treat it as normal, returning kRanOutOfSurfaces from Decode().
virtual scoped_refptr<AV1Picture> CreateAV1Picture(bool apply_grain) = 0;
// Submits |pic| to the driver for accelerated decoding. The following
// parameters are also passed:
// - |sequence_header|: the current OBU sequence header.
// - |ref_frames|: the pictures used as reference for decoding |pic|.
// - |tile_buffers|: tile information.
// - |data|: the entire data of the DecoderBuffer set by
// AV1Decoder::SetStream().
// Note that returning from this method does not mean that the decode
// process is finished, but the caller may drop its references to |pic|
// and |ref_frames| immediately, and |data| does not need to remain valid
// after this method returns.
// Returns true when successful, false otherwise.
virtual bool SubmitDecode(
const AV1Picture& pic,
const libgav1::ObuSequenceHeader& sequence_header,
const AV1ReferenceFrameVector& ref_frames,
const libgav1::Vector<libgav1::TileBuffer>& tile_buffers,
base::span<const uint8_t> data) = 0;
// Schedules output (display) of |pic|.
// Note that returning from this method does not mean that |pic| has already
// been outputted (displayed), but guarantees that all pictures will be
// outputted in the same order as this method was called for them, and that
// they are decoded before outputting (assuming SubmitDecode() has been
// called for them beforehand).
// Returns true when successful, false otherwise.
virtual bool OutputPicture(const AV1Picture& pic) = 0;
};
AV1Decoder(std::unique_ptr<AV1Accelerator> accelerator,
VideoCodecProfile profile);
~AV1Decoder() override;
AV1Decoder(const AV1Decoder&) = delete;
AV1Decoder& operator=(const AV1Decoder&) = delete;
// AcceleratedVideoDecoder implementation.
void SetStream(int32_t id, const DecoderBuffer& decoder_buffer) override;
bool Flush() override WARN_UNUSED_RESULT;
void Reset() override;
DecodeResult Decode() override WARN_UNUSED_RESULT;
gfx::Size GetPicSize() const override;
gfx::Rect GetVisibleRect() const override;
VideoCodecProfile GetProfile() const override;
size_t GetRequiredNumOfPictures() const override;
size_t GetNumReferenceFrames() const override;
private:
// Returns whether the current stream contains a new OBU sequence header.
bool HasNewSequenceHeader() const;
bool DecodeAndOutputPicture(
scoped_refptr<AV1Picture> pic,
const libgav1::Vector<libgav1::TileBuffer>& tile_buffers);
void UpdateReferenceFrames(scoped_refptr<AV1Picture> pic);
void ClearReferenceFrames();
bool CheckAndCleanUpReferenceFrames();
void ClearCurrentFrame();
DecodeResult DecodeInternal();
bool on_error_ = false;
std::unique_ptr<libgav1::BufferPool> buffer_pool_;
std::unique_ptr<libgav1::DecoderState> state_;
std::unique_ptr<libgav1::ObuParser> parser_;
const std::unique_ptr<AV1Accelerator> accelerator_;
AV1ReferenceFrameVector ref_frames_;
base::Optional<libgav1::ObuSequenceHeader> current_sequence_header_;
base::Optional<libgav1::ObuFrameHeader> current_frame_header_;
libgav1::RefCountedBufferPtr current_frame_;
gfx::Rect visible_rect_;
gfx::Size frame_size_;
VideoCodecProfile profile_;
int32_t stream_id_ = 0;
const uint8_t* stream_ = nullptr;
size_t stream_size_ = 0;
SEQUENCE_CHECKER(sequence_checker_);
};
} // namespace media
#endif // MEDIA_GPU_AV1_DECODER_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/av1_decoder.h"
#include <string.h>
#include <string>
#include <vector>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "media/base/decoder_buffer.h"
#include "media/base/test_data_util.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/ffmpeg_demuxer.h"
#include "media/filters/in_memory_url_protocol.h"
#include "media/filters/ivf_parser.h"
#include "media/gpu/av1_picture.h"
#include "media/media_buildflags.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libgav1/src/src/obu_parser.h"
#include "third_party/libgav1/src/src/utils/constants.h"
#include "third_party/libgav1/src/src/utils/types.h"
#if !BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
#error "This test requires Chrome OS media acceleration"
#endif
#include "media/gpu/chromeos/fourcc.h"
using ::testing::_;
using ::testing::Return;
namespace media {
namespace {
class FakeAV1Picture : public AV1Picture {
public:
FakeAV1Picture() = default;
protected:
~FakeAV1Picture() override = default;
private:
scoped_refptr<AV1Picture> CreateDuplicate() override {
return base::MakeRefCounted<FakeAV1Picture>();
}
};
bool IsYUV420(int8_t subsampling_x, int8_t subsampling_y, bool is_monochrome) {
return subsampling_x == 1 && subsampling_y == 1 && !is_monochrome;
}
MATCHER_P(SameAV1PictureInstance, av1_picture, "") {
return &arg == av1_picture.get();
}
MATCHER_P2(MatchesFrameSizeAndRenderSize, frame_size, render_size, "") {
const auto& frame_header = arg.frame_header;
return base::strict_cast<int>(frame_header.width) == frame_size.width() &&
base::strict_cast<int>(frame_header.height) == frame_size.height() &&
base::strict_cast<int>(frame_header.render_width) ==
render_size.width() &&
base::strict_cast<int>(frame_header.render_height) ==
render_size.height();
}
MATCHER_P4(MatchesFrameHeader,
frame_size,
render_size,
show_existing_frame,
show_frame,
"") {
const auto& frame_header = arg.frame_header;
return base::strict_cast<int>(frame_header.width) == frame_size.width() &&
base::strict_cast<int>(frame_header.height) == frame_size.height() &&
base::strict_cast<int>(frame_header.render_width) ==
render_size.width() &&
base::strict_cast<int>(frame_header.render_height) ==
render_size.height() &&
frame_header.show_existing_frame == show_existing_frame &&
frame_header.show_frame == show_frame;
}
MATCHER_P4(MatchesYUV420SequenceHeader,
profile,
bitdepth,
max_frame_size,
film_grain_params_present,
"") {
return arg.profile == profile && arg.color_config.bitdepth == bitdepth &&
base::strict_cast<int>(arg.max_frame_width) ==
max_frame_size.width() &&
base::strict_cast<int>(arg.max_frame_height) ==
max_frame_size.height() &&
arg.film_grain_params_present == film_grain_params_present &&
IsYUV420(arg.color_config.subsampling_x,
arg.color_config.subsampling_y,
arg.color_config.is_monochrome);
}
MATCHER(NonEmptyTileBuffers, "") {
return !arg.empty();
}
MATCHER_P(MatchesFrameData, decoder_buffer, "") {
return arg.data() == decoder_buffer->data() &&
arg.size() == decoder_buffer->data_size();
}
class MockAV1Accelerator : public AV1Decoder::AV1Accelerator {
public:
MockAV1Accelerator() = default;
~MockAV1Accelerator() override = default;
MOCK_METHOD1(CreateAV1Picture, scoped_refptr<AV1Picture>(bool));
MOCK_METHOD5(SubmitDecode,
bool(const AV1Picture&,
const libgav1::ObuSequenceHeader&,
const AV1ReferenceFrameVector&,
const libgav1::Vector<libgav1::TileBuffer>&,
base::span<const uint8_t>));
MOCK_METHOD1(OutputPicture, bool(const AV1Picture&));
};
class AV1DecoderTest : public ::testing::Test {
public:
using DecodeResult = AcceleratedVideoDecoder::DecodeResult;
AV1DecoderTest() = default;
~AV1DecoderTest() override = default;
void SetUp() override;
std::vector<DecodeResult> Decode(scoped_refptr<DecoderBuffer> buffer);
scoped_refptr<DecoderBuffer> ReadDecoderBuffer(const std::string& fname);
std::vector<scoped_refptr<DecoderBuffer>> ReadIVF(const std::string& fname);
std::vector<scoped_refptr<DecoderBuffer>> ReadWebm(const std::string& fname);
protected:
base::FilePath GetTestFilePath(const std::string& fname) {
base::FilePath file_path(base::FilePath(base::FilePath::kCurrentDirectory)
.Append(base::FilePath::StringType(fname)));
if (base::PathExists(file_path)) {
return file_path;
}
return GetTestDataFilePath(fname);
}
// Owned by |decoder_|.
MockAV1Accelerator* mock_accelerator_;
std::unique_ptr<AV1Decoder> decoder_;
int32_t bitstream_id_ = 0;
};
void AV1DecoderTest::SetUp() {
auto accelerator = std::make_unique<MockAV1Accelerator>();
mock_accelerator_ = accelerator.get();
decoder_ = std::make_unique<AV1Decoder>(std::move(accelerator),
VIDEO_CODEC_PROFILE_UNKNOWN);
}
std::vector<AcceleratedVideoDecoder::DecodeResult> AV1DecoderTest::Decode(
scoped_refptr<DecoderBuffer> buffer) {
decoder_->SetStream(bitstream_id_++, *buffer);
std::vector<DecodeResult> results;
DecodeResult res;
do {
res = decoder_->Decode();
results.push_back(res);
} while (res != DecodeResult::kDecodeError &&
res != DecodeResult::kRanOutOfStreamData);
return results;
} // namespace
scoped_refptr<DecoderBuffer> AV1DecoderTest::ReadDecoderBuffer(
const std::string& fname) {
auto input_file = GetTestFilePath(fname);
std::string bitstream;
EXPECT_TRUE(base::ReadFileToString(input_file, &bitstream));
auto buffer = DecoderBuffer::CopyFrom(
reinterpret_cast<const uint8_t*>(bitstream.data()), bitstream.size());
EXPECT_TRUE(!!buffer);
return buffer;
}
std::vector<scoped_refptr<DecoderBuffer>> AV1DecoderTest::ReadIVF(
const std::string& fname) {
std::string ivf_data;
auto input_file = GetTestFilePath(fname);
EXPECT_TRUE(base::ReadFileToString(input_file, &ivf_data));
IvfParser ivf_parser;
IvfFileHeader ivf_header{};
EXPECT_TRUE(
ivf_parser.Initialize(reinterpret_cast<const uint8_t*>(ivf_data.data()),
ivf_data.size(), &ivf_header));
EXPECT_EQ(ivf_header.fourcc, ComposeFourcc('A', 'V', '0', '1'));
std::vector<scoped_refptr<DecoderBuffer>> buffers;
IvfFrameHeader ivf_frame_header{};
const uint8_t* data;
while (ivf_parser.ParseNextFrame(&ivf_frame_header, &data)) {
buffers.push_back(DecoderBuffer::CopyFrom(
reinterpret_cast<const uint8_t*>(data), ivf_frame_header.frame_size));
}
return buffers;
}
std::vector<scoped_refptr<DecoderBuffer>> AV1DecoderTest::ReadWebm(
const std::string& fname) {
std::string webm_data;
auto input_file = GetTestFilePath(fname);
EXPECT_TRUE(base::ReadFileToString(input_file, &webm_data));
InMemoryUrlProtocol protocol(
reinterpret_cast<const uint8_t*>(webm_data.data()), webm_data.size(),
false);
FFmpegGlue glue(&protocol);
LOG_ASSERT(glue.OpenContext());
int stream_index = -1;
for (unsigned int i = 0; i < glue.format_context()->nb_streams; ++i) {
const AVStream* stream = glue.format_context()->streams[i];
const AVCodecParameters* codec_parameters = stream->codecpar;
const AVMediaType codec_type = codec_parameters->codec_type;
const AVCodecID codec_id = codec_parameters->codec_id;
if (codec_type == AVMEDIA_TYPE_VIDEO && codec_id == AV_CODEC_ID_AV1) {
stream_index = i;
break;
}
}
EXPECT_NE(stream_index, -1) << "No AV1 data found in " << input_file;
std::vector<scoped_refptr<DecoderBuffer>> buffers;
AVPacket packet{};
while (av_read_frame(glue.format_context(), &packet) >= 0) {
if (packet.stream_index == stream_index)
buffers.push_back(DecoderBuffer::CopyFrom(packet.data, packet.size));
av_packet_unref(&packet);
}
return buffers;
}
TEST_F(AV1DecoderTest, DecodeInvalidOBU) {
std::string kInvalidData = "ThisIsInvalidData";
auto kInvalidBuffer = DecoderBuffer::CopyFrom(
reinterpret_cast<const uint8_t*>(kInvalidData.data()),
kInvalidData.size());
std::vector<DecodeResult> results = Decode(kInvalidBuffer);
std::vector<DecodeResult> expected = {DecodeResult::kDecodeError};
EXPECT_EQ(results, expected);
}
TEST_F(AV1DecoderTest, DecodeEmptyOBU) {
auto kEmptyBuffer = base::MakeRefCounted<DecoderBuffer>(0);
std::vector<DecodeResult> results = Decode(kEmptyBuffer);
std::vector<DecodeResult> expected = {DecodeResult::kRanOutOfStreamData};
EXPECT_EQ(results, expected);
}
TEST_F(AV1DecoderTest, DecodeOneIFrame) {
constexpr gfx::Size kFrameSize(320, 240);
constexpr gfx::Size kRenderSize(320, 240);
constexpr auto kProfile = libgav1::BitstreamProfile::kProfile0;
const std::string kIFrame("av1-I-frame-320x240");
scoped_refptr<DecoderBuffer> i_frame_buffer = ReadDecoderBuffer(kIFrame);
ASSERT_TRUE(!!i_frame_buffer);
auto av1_picture = base::MakeRefCounted<AV1Picture>();
::testing::InSequence s;
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(i_frame_buffer)))
.WillOnce(Return(true));
EXPECT_CALL(*mock_accelerator_,
OutputPicture(SameAV1PictureInstance(av1_picture)))
.WillOnce(Return(true));
std::vector<DecodeResult> results = Decode(i_frame_buffer);
std::vector<DecodeResult> expected = {DecodeResult::kConfigChange,
DecodeResult::kRanOutOfStreamData};
EXPECT_EQ(results, expected);
}
TEST_F(AV1DecoderTest, DecodeSimpleStream) {
constexpr gfx::Size kFrameSize(320, 240);
constexpr gfx::Size kRenderSize(320, 240);
constexpr auto kProfile = libgav1::BitstreamProfile::kProfile0;
const std::string kSimpleStream("bear-av1.webm");
std::vector<scoped_refptr<DecoderBuffer>> buffers = ReadWebm(kSimpleStream);
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(true));
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);
}
TEST_F(AV1DecoderTest, DecodeShowExistingPictureStream) {
constexpr gfx::Size kFrameSize(208, 144);
constexpr gfx::Size kRenderSize(208, 144);
constexpr auto kProfile = libgav1::BitstreamProfile::kProfile0;
constexpr size_t kDecodedFrames = 10;
constexpr size_t kOutputFrames = 10;
const std::string kShowExistingFrameStream("av1-show_existing_frame.ivf");
std::vector<scoped_refptr<DecoderBuffer>> buffers =
ReadIVF(kShowExistingFrameStream);
ASSERT_FALSE(buffers.empty());
// TODO(hiroh): Test what's unique about the show_existing_frame path.
std::vector<DecodeResult> expected = {DecodeResult::kConfigChange};
std::vector<DecodeResult> results;
EXPECT_CALL(*mock_accelerator_, CreateAV1Picture(/*apply_grain=*/false))
.Times(kDecodedFrames)
.WillRepeatedly(Return(base::MakeRefCounted<FakeAV1Picture>()));
EXPECT_CALL(
*mock_accelerator_,
SubmitDecode(
MatchesFrameSizeAndRenderSize(kFrameSize, kRenderSize),
MatchesYUV420SequenceHeader(kProfile, /*bitdepth=*/8, kFrameSize,
/*film_grain_params_present=*/false),
_, NonEmptyTileBuffers(), _))
.Times(kDecodedFrames)
.WillRepeatedly(Return(true));
EXPECT_CALL(*mock_accelerator_, OutputPicture(_))
.Times(kOutputFrames)
.WillRepeatedly(Return(true));
for (auto buffer : buffers) {
for (DecodeResult r : Decode(buffer))
results.push_back(r);
expected.push_back(DecodeResult::kRanOutOfStreamData);
}
EXPECT_EQ(results, expected);
}
TEST_F(AV1DecoderTest, Decode10bitStream) {
const std::string k10bitStream("bear-av1-320x180-10bit.webm");
std::vector<scoped_refptr<DecoderBuffer>> buffers = ReadWebm(k10bitStream);
ASSERT_FALSE(buffers.empty());
std::vector<DecodeResult> expected = {DecodeResult::kDecodeError};
EXPECT_EQ(Decode(buffers[0]), expected);
// Once AV1Decoder gets into an error state, Decode() returns kDecodeError
// until Reset().
EXPECT_EQ(Decode(buffers[1]), expected);
}
TEST_F(AV1DecoderTest, DecodeSVCStream) {
const std::string kSVCStream("av1-svc-L2T2.ivf");
std::vector<scoped_refptr<DecoderBuffer>> buffers = ReadIVF(kSVCStream);
ASSERT_FALSE(buffers.empty());
std::vector<DecodeResult> expected = {DecodeResult::kDecodeError};
EXPECT_EQ(Decode(buffers[0]), expected);
// Once AV1Decoder gets into an error state, Decode() returns kDecodeError
// until Reset().
EXPECT_EQ(Decode(buffers[1]), expected);
}
// TODO(hiroh): Add more tests, non-YUV420 stream, Reset() flow, mid-stream
// configuration change, and reference frame tracking.
} // namespace
} // namespace media
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/av1_picture.h"
#include <memory>
namespace media {
AV1Picture::AV1Picture() = default;
AV1Picture::~AV1Picture() = default;
scoped_refptr<AV1Picture> AV1Picture::Duplicate() {
scoped_refptr<AV1Picture> dup_pic = CreateDuplicate();
if (!dup_pic)
return nullptr;
// Copy members of AV1Picture and CodecPicture.
// A proper bitstream id is set in AV1Decoder.
// Note that decrypt_config_ is not used in here, so skip copying it.
dup_pic->frame_header = frame_header;
dup_pic->set_bitstream_id(bitstream_id());
dup_pic->set_visible_rect(visible_rect());
dup_pic->set_colorspace(get_colorspace());
return dup_pic;
}
scoped_refptr<AV1Picture> AV1Picture::CreateDuplicate() {
return nullptr;
}
} // namespace media
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_GPU_AV1_PICTURE_H_
#define MEDIA_GPU_AV1_PICTURE_H_
#include <memory>
#include "media/gpu/codec_picture.h"
#include "media/gpu/media_gpu_export.h"
#include "third_party/libgav1/src/src/utils/types.h"
namespace media {
// AV1Picture carries the parsed frame header needed for decoding an AV1 frame.
// It also owns the decoded frame itself.
class MEDIA_GPU_EXPORT AV1Picture : public CodecPicture {
public:
AV1Picture();
AV1Picture(const AV1Picture&) = delete;
AV1Picture& operator=(const AV1Picture&) = delete;
// Create a duplicate instance and copy the data to it. It is used to support
// the AV1 show_existing_frame feature. Return the scoped_refptr pointing to
// the duplicate instance, or nullptr on failure.
scoped_refptr<AV1Picture> Duplicate();
libgav1::ObuFrameHeader frame_header = {};
protected:
~AV1Picture() override;
private:
// Create a duplicate instance.
virtual scoped_refptr<AV1Picture> CreateDuplicate();
};
} // namespace media
#endif // MEDIA_GPU_AV1_PICTURE_H_
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