Commit b5949715 authored by Jeffrey Kardatzke's avatar Jeffrey Kardatzke Committed by Commit Bot

Add remaining H265Decoder logic

This completes the H265Decoder by adding picture output flags, picture
order counts, ref pic list calculations and DPB management operations.

The unit tests were updated for this and a fuzzer was also added for
the decoder.

BUG=chromium:1141237,b:153111783
TEST=media_unittests, media_h265_decoder_fuzzer

Change-Id: Ieaa1da24d1e93ea017e1d5d68e67e07596347879
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2521113
Commit-Queue: Jeffrey Kardatzke <jkardatzke@google.com>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Reviewed-by: default avatarSergey Volk <servolk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#825443}
parent e1903b7a
......@@ -607,3 +607,14 @@ if (use_v4l2_codec || use_vaapi) {
]
}
}
if (proprietary_codecs && enable_platform_hevc) {
fuzzer_test("media_h265_decoder_fuzzer") {
sources = [ "h265_decoder_fuzzertest.cc" ]
deps = [
":gpu",
"//base",
"//media",
]
}
}
This diff is collapsed.
......@@ -124,6 +124,14 @@ class MEDIA_GPU_EXPORT H265Decoder final : public AcceleratedVideoDecoder {
// the accelerator needs additional data before being able to proceed.
virtual Status SubmitDecode(scoped_refptr<H265Picture> pic) = 0;
// Schedule 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. Decoder may drop its reference
// to |pic| after calling this method.
// Return true if successful.
virtual bool OutputPicture(scoped_refptr<H265Picture> pic) = 0;
// Reset any current state that may be cached in the accelerator, dropping
// any cached parameters/slices that have not been committed yet.
virtual void Reset() = 0;
......@@ -205,6 +213,42 @@ class MEDIA_GPU_EXPORT H265Decoder final : public AcceleratedVideoDecoder {
// Commits all pending data for HW decoder and starts HW decoder.
H265Accelerator::Status DecodePicture();
// Notifies client that a picture is ready for output.
bool OutputPic(scoped_refptr<H265Picture> pic);
// Output all pictures in DPB that have not been outputted yet.
bool OutputAllRemainingPics();
// Calculates the picture output flags using |slice_hdr| for |curr_pic_|.
void CalcPicOutputFlags(const H265SliceHeader* slice_hdr);
// Calculates picture order count (POC) using |pps| and|slice_hdr| for
// |curr_pic_|.
void CalcPictureOrderCount(const H265PPS* pps,
const H265SliceHeader* slice_hdr);
// Calculates the POCs for the reference pictures for |curr_pic_| using
// |sps|, |pps| and |slice_hdr| and stores them in the member variables.
// Returns false if bitstream conformance is not maintained, true otherwise.
bool CalcRefPicPocs(const H265SPS* sps,
const H265PPS* pps,
const H265SliceHeader* slice_hdr);
// Builds the reference pictures lists for |curr_pic_| using |sps|, |pps|,
// |slice_hdr| and the member variables calculated in CalcRefPicPocs. Returns
// false if bitstream conformance is not maintained or needed reference
// pictures are missing, true otherwise. At the end of this,
// |ref_pic_list{0,1}| will be populated with the required reference pictures
// for submitting to the accelerator.
bool BuildRefPicLists(const H265SPS* sps,
const H265PPS* pps,
const H265SliceHeader* slice_hdr);
// Performs DPB management operations for |curr_pic_| by removing no longer
// needed entries from the DPB and outputting pictures from the DPB. |sps|
// should be the corresponding SPS for |curr_pic_|.
bool PerformDpbOperations(const H265SPS* sps);
// Decoder state.
State state_;
......@@ -241,6 +285,18 @@ class MEDIA_GPU_EXPORT H265Decoder final : public AcceleratedVideoDecoder {
// Global state values, needed in decoding. See spec.
scoped_refptr<H265Picture> prev_tid0_pic_;
int max_pic_order_cnt_lsb_;
bool curr_delta_poc_msb_present_flag_[kMaxDpbSize];
bool foll_delta_poc_msb_present_flag_[kMaxDpbSize];
int num_poc_st_curr_before_;
int num_poc_st_curr_after_;
int num_poc_st_foll_;
int num_poc_lt_curr_;
int num_poc_lt_foll_;
int poc_st_curr_before_[kMaxDpbSize];
int poc_st_curr_after_[kMaxDpbSize];
int poc_st_foll_[kMaxDpbSize];
int poc_lt_curr_[kMaxDpbSize];
int poc_lt_foll_[kMaxDpbSize];
H265Picture::Vector ref_pic_list0_;
H265Picture::Vector ref_pic_list1_;
......
// 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 <stddef.h>
#include "base/numerics/safe_conversions.h"
#include "media/base/decoder_buffer.h"
#include "media/base/video_codecs.h"
#include "media/gpu/h265_decoder.h"
namespace {
class FakeH265Accelerator : public media::H265Decoder::H265Accelerator {
public:
FakeH265Accelerator() = default;
FakeH265Accelerator(const FakeH265Accelerator&) = delete;
FakeH265Accelerator& operator=(const FakeH265Accelerator&) = delete;
~FakeH265Accelerator() override = default;
// media::H265Decoder::H265Accelerator
scoped_refptr<media::H265Picture> CreateH265Picture() override {
return new media::H265Picture();
}
Status SubmitFrameMetadata(const media::H265SPS* sps,
const media::H265PPS* pps,
const media::H265SliceHeader* slice_hdr,
const media::H265Picture::Vector& ref_pic_list,
scoped_refptr<media::H265Picture> pic) override {
return Status::kOk;
}
Status SubmitSlice(
const media::H265SPS* sps,
const media::H265PPS* pps,
const media::H265SliceHeader* slice_hdr,
const media::H265Picture::Vector& ref_pic_list0,
const media::H265Picture::Vector& ref_pic_list1,
scoped_refptr<media::H265Picture> pic,
const uint8_t* data,
size_t size,
const std::vector<media::SubsampleEntry>& subsamples) override {
return Status::kOk;
}
Status SubmitDecode(scoped_refptr<media::H265Picture> pic) override {
return Status::kOk;
}
bool OutputPicture(scoped_refptr<media::H265Picture> pic) override {
return true;
}
void Reset() override {}
Status SetStream(base::span<const uint8_t> stream,
const media::DecryptConfig* decrypt_config) override {
return Status::kOk;
}
};
} // namespace
// Entry point for LibFuzzer.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (!size)
return 0;
media::H265Decoder decoder(std::make_unique<FakeH265Accelerator>(),
media::HEVCPROFILE_MAIN);
scoped_refptr<media::DecoderBuffer> decoder_buffer =
media::DecoderBuffer::CopyFrom(data, size);
decoder.SetStream(1, *decoder_buffer);
// Decode should consume all the data unless it returns kConfigChange, and in
// that case it needs to be called again.
while (true) {
if (decoder.Decode() != media::AcceleratedVideoDecoder::kConfigChange)
break;
}
return 0;
}
......@@ -55,6 +55,31 @@ MATCHER(SubsampleSizeMatches, "Verify subsample sizes match buffer size") {
return subsample_total_size == buffer_size;
}
// To have better description on mismatch.
class HasPocMatcher : public MatcherInterface<scoped_refptr<H265Picture>> {
public:
explicit HasPocMatcher(int expected_poc) : expected_poc_(expected_poc) {}
bool MatchAndExplain(scoped_refptr<H265Picture> p,
MatchResultListener* listener) const override {
if (p->pic_order_cnt_val_ == expected_poc_)
return true;
*listener << "with poc: " << p->pic_order_cnt_val_;
return false;
}
void DescribeTo(std::ostream* os) const override {
*os << "with poc " << expected_poc_;
}
private:
int expected_poc_;
};
Matcher<scoped_refptr<H265Picture>> HasPoc(int expected_poc) {
return MakeMatcher(new HasPocMatcher(expected_poc));
}
} // namespace
class MockH265Accelerator : public H265Decoder::H265Accelerator {
......@@ -79,6 +104,7 @@ class MockH265Accelerator : public H265Decoder::H265Accelerator {
size_t size,
const std::vector<SubsampleEntry>& subsamples));
MOCK_METHOD1(SubmitDecode, Status(scoped_refptr<H265Picture> pic));
MOCK_METHOD1(OutputPicture, bool(scoped_refptr<H265Picture>));
MOCK_METHOD2(SetStream,
Status(base::span<const uint8_t> stream,
const DecryptConfig* decrypt_config));
......@@ -128,6 +154,7 @@ void H265DecoderTest::SetUp() {
.WillByDefault(Return(H265Decoder::H265Accelerator::Status::kOk));
ON_CALL(*accelerator_, SubmitDecode(_))
.WillByDefault(Return(H265Decoder::H265Accelerator::Status::kOk));
ON_CALL(*accelerator_, OutputPicture(_)).WillByDefault(Return(true));
ON_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _, _))
.With(Args<7, 8>(SubsampleSizeMatches()))
.WillByDefault(Return(H265Decoder::H265Accelerator::Status::kOk));
......@@ -162,8 +189,6 @@ AcceleratedVideoDecoder::DecodeResult H265DecoderTest::Decode(
}
}
// TODO(jkardatzke): Update all test cases to handle OutputPicture and POC when
// we add that to the decoder.
TEST_F(H265DecoderTest, DecodeSingleFrame) {
SetInputFrameFiles({kSpsPps, kFrame0});
EXPECT_EQ(AcceleratedVideoDecoder::kConfigChange, Decode());
......@@ -181,7 +206,8 @@ TEST_F(H265DecoderTest, DecodeSingleFrame) {
EXPECT_CALL(*accelerator_, CreateH265Picture()).Times(1);
EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _)).Times(1);
EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _, _)).Times(1);
EXPECT_CALL(*accelerator_, SubmitDecode(_)).Times(1);
EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(0))).Times(1);
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(0)));
}
EXPECT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode());
EXPECT_TRUE(decoder_->Flush());
......@@ -198,7 +224,8 @@ TEST_F(H265DecoderTest, SkipNonIDRFrames) {
EXPECT_CALL(*accelerator_, CreateH265Picture()).Times(1);
EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _)).Times(1);
EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _, _)).Times(1);
EXPECT_CALL(*accelerator_, SubmitDecode(_)).Times(1);
EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(0))).Times(1);
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(0)));
}
EXPECT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode());
EXPECT_TRUE(decoder_->Flush());
......@@ -215,12 +242,44 @@ TEST_F(H265DecoderTest, DecodeProfileMain) {
EXPECT_CALL(*accelerator_, CreateH265Picture()).Times(6);
EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _)).Times(6);
EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _, _)).Times(6);
EXPECT_CALL(*accelerator_, SubmitDecode(_)).Times(6);
Expectation decode_poc0, decode_poc1, decode_poc2, decode_poc3, decode_poc4,
decode_poc8;
{
InSequence decode_order;
decode_poc0 = EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(0)));
decode_poc4 = EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(4)));
decode_poc2 = EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(2)));
decode_poc1 = EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(1)));
decode_poc3 = EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(3)));
decode_poc8 = EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(8)));
}
{
InSequence display_order;
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(0))).After(decode_poc0);
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(1))).After(decode_poc1);
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(2))).After(decode_poc2);
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(3))).After(decode_poc3);
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(4))).After(decode_poc4);
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(8))).After(decode_poc8);
}
EXPECT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode());
EXPECT_TRUE(decoder_->Flush());
}
TEST_F(H265DecoderTest, OutputPictureFailureCausesDecodeToFail) {
// Provide enough data that Decode() will try to output a frame.
SetInputFrameFiles({kSpsPps, kFrame0, kFrame1, kFrame2, kFrame3});
EXPECT_EQ(AcceleratedVideoDecoder::kConfigChange, Decode());
EXPECT_CALL(*accelerator_, CreateH265Picture()).Times(4);
EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _)).Times(3);
EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _, _)).Times(3);
EXPECT_CALL(*accelerator_, SubmitDecode(_)).Times(3);
EXPECT_CALL(*accelerator_, OutputPicture(_)).WillRepeatedly(Return(false));
EXPECT_EQ(AcceleratedVideoDecoder::kDecodeError, Decode());
}
// Verify that the decryption config is passed to the accelerator.
TEST_F(H265DecoderTest, SetEncryptedStream) {
std::string bitstream, bitstream1, bitstream2;
......@@ -286,7 +345,8 @@ TEST_F(H265DecoderTest, SubmitFrameMetadataRetry) {
InSequence sequence;
EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _));
EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _, _));
EXPECT_CALL(*accelerator_, SubmitDecode(_));
EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(0)));
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(0)));
}
EXPECT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode());
......@@ -321,7 +381,8 @@ TEST_F(H265DecoderTest, SubmitSliceRetry) {
{
InSequence sequence;
EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _, _));
EXPECT_CALL(*accelerator_, SubmitDecode(_));
EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(0)));
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(0)));
}
EXPECT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode());
EXPECT_TRUE(decoder_->Flush());
......@@ -339,7 +400,7 @@ TEST_F(H265DecoderTest, SubmitDecodeRetry) {
EXPECT_CALL(*accelerator_, CreateH265Picture());
EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _));
EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _, _));
EXPECT_CALL(*accelerator_, SubmitDecode(_))
EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(0)))
.WillOnce(Return(H265Decoder::H265Accelerator::Status::kTryAgain));
}
EXPECT_EQ(AcceleratedVideoDecoder::kTryAgain, Decode());
......@@ -349,7 +410,7 @@ TEST_F(H265DecoderTest, SubmitDecodeRetry) {
EXPECT_CALL(*accelerator_, CreateH265Picture()).Times(0);
EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _)).Times(0);
EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _, _)).Times(0);
EXPECT_CALL(*accelerator_, SubmitDecode(_))
EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(0)))
.WillOnce(Return(H265Decoder::H265Accelerator::Status::kTryAgain));
EXPECT_EQ(AcceleratedVideoDecoder::kTryAgain, Decode());
......@@ -357,11 +418,13 @@ TEST_F(H265DecoderTest, SubmitDecodeRetry) {
// the first frame.
{
InSequence sequence;
EXPECT_CALL(*accelerator_, SubmitDecode(_));
EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(0)));
EXPECT_CALL(*accelerator_, CreateH265Picture());
EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _));
EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _, _));
EXPECT_CALL(*accelerator_, SubmitDecode(_));
EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(4)));
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(0)));
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(4)));
}
EXPECT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode());
EXPECT_TRUE(decoder_->Flush());
......@@ -386,7 +449,8 @@ TEST_F(H265DecoderTest, SetStreamRetry) {
EXPECT_CALL(*accelerator_, CreateH265Picture()).Times(1);
EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _)).Times(1);
EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _, _)).Times(1);
EXPECT_CALL(*accelerator_, SubmitDecode(_)).Times(1);
EXPECT_CALL(*accelerator_, SubmitDecode(HasPoc(0))).Times(1);
EXPECT_CALL(*accelerator_, OutputPicture(HasPoc(0))).Times(1);
}
EXPECT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode());
EXPECT_TRUE(decoder_->Flush());
......
......@@ -27,4 +27,77 @@ void H265DPB::Clear() {
pics_.clear();
}
void H265DPB::StorePicture(scoped_refptr<H265Picture> pic,
H265Picture::ReferenceType ref) {
DCHECK_LT(pics_.size(), max_num_pics_);
pic->ref_ = ref;
DVLOG(3) << "Adding PicNum: " << pic->pic_order_cnt_val_
<< " ref: " << static_cast<int>(pic->ref_);
pics_.push_back(std::move(pic));
}
void H265DPB::MarkAllUnusedForReference() {
for (const auto& pic : pics_)
pic->ref_ = H265Picture::kUnused;
}
void H265DPB::DeleteUnused() {
for (auto it = pics_.begin(); it != pics_.end();) {
auto& pic = *it;
if ((!pic->pic_output_flag_ || pic->outputted_) &&
(pic->ref_ == H265Picture::kUnused)) {
std::swap(pic, *(pics_.end() - 1));
pics_.pop_back();
} else {
it++;
}
}
}
int H265DPB::GetReferencePicCount() {
int count = 0;
for (const auto& pic : pics_) {
if (pic->ref_ != H265Picture::kUnused)
count++;
}
return count;
}
scoped_refptr<H265Picture> H265DPB::GetPicByPocAndMark(
int poc,
H265Picture::ReferenceType ref) {
return GetPicByPocMaskedAndMark(poc, 0, ref);
}
scoped_refptr<H265Picture> H265DPB::GetPicByPocMaskedAndMark(
int poc,
int mask,
H265Picture::ReferenceType ref) {
for (const auto& pic : pics_) {
if ((mask && (pic->pic_order_cnt_val_ & mask) == poc) ||
(!mask && pic->pic_order_cnt_val_ == poc)) {
pic->ref_ = ref;
return pic;
}
}
DVLOG(1) << "Missing " << H265Picture::GetReferenceName(ref)
<< " ref pic num: " << poc;
return nullptr;
}
void H265DPB::AppendPendingOutputPics(H265Picture::Vector* out) {
for (const auto& pic : pics_) {
if (pic->pic_output_flag_ && !pic->outputted_)
out->push_back(pic);
}
}
void H265DPB::AppendReferencePics(H265Picture::Vector* out) {
for (const auto& pic : pics_) {
if (pic->ref_ != H265Picture::kUnused)
out->push_back(pic);
}
}
} // namespace media
\ No newline at end of file
......@@ -67,6 +67,7 @@ class MEDIA_GPU_EXPORT H265Picture : public CodecPicture {
// Our own state variables.
bool irap_pic_;
bool first_picture_;
bool processed_{false};
ReferenceType ref_{kUnused};
......@@ -94,6 +95,41 @@ class H265DPB {
// Removes all entries from the DPB.
void Clear();
// Stores |pic| in the DPB. If |used_for_long_term| is true it'll be marked as
// used for long term reference, otherwise it'll be marked as used for short
// term reference.
void StorePicture(scoped_refptr<H265Picture> pic,
H265Picture::ReferenceType ref);
// Mark all pictures in DPB as unused for reference.
void MarkAllUnusedForReference();
// Removes all pictures from the DPB that do not have |pic_output_flag_| set
// and are marked Unused for reference.
void DeleteUnused();
// Returns the number of pictures in the DPB that are marked for reference.
int GetReferencePicCount();
// Returns a picture in the DPB which has a POC equal to |poc| and marks it
// with |ref| reference type. If not found, returns nullptr.
scoped_refptr<H265Picture> GetPicByPocAndMark(int poc,
H265Picture::ReferenceType ref);
// Returns a picture in the DPB which has a POC bitmasked by |mask| which
// equals |poc| and marks it with |ref| reference type. If not found, returns
// nullptr. If |mask| is zero, then no bitmasking is done.
scoped_refptr<H265Picture>
GetPicByPocMaskedAndMark(int poc, int mask, H265Picture::ReferenceType ref);
// Appends to |out| all of the pictures in the DPB that are flagged for output
// but have not be outputted yet.
void AppendPendingOutputPics(H265Picture::Vector* out);
// Appends to |out| all of the pictures in the DPB that are not marked as
// unused for reference.
void AppendReferencePics(H265Picture::Vector* out);
size_t size() const { return pics_.size(); }
bool IsFull() const { return pics_.size() >= max_num_pics_; }
......
......@@ -155,8 +155,6 @@ fuzzer_test("media_h264_parser_fuzzer") {
]
}
# TODO(jkardatzke): Get the fuzzer build config updated so this target will
# be included.
if (proprietary_codecs && enable_platform_hevc) {
fuzzer_test("media_h265_parser_fuzzer") {
sources = [ "h265_parser_fuzzertest.cc" ]
......
......@@ -5,10 +5,7 @@
#include <stddef.h>
#include "base/numerics/safe_conversions.h"
#include "base/optional.h"
#include "media/video/h265_parser.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
// Entry point for LibFuzzer.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
......
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