Commit 7293b5dd authored by Jeffrey Kardatzke's avatar Jeffrey Kardatzke Committed by Commit Bot

Initial H265 decoder implementation

This adds the H265Decoder (modeled after the H264Decoder) with only
handling of SPS/SPS.

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

Change-Id: Ia2dc95675b9c200626e96c500ed91fb4ba27b6dd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2515040Reviewed-by: default avatarSergey Volk <servolk@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Commit-Queue: Jeffrey Kardatzke <jkardatzke@google.com>
Cr-Commit-Position: refs/heads/master@{#823569}
parent 1ddfef27
......@@ -270,6 +270,15 @@ source_set("common") {
"vp9_reference_frame_vector.h",
]
if (proprietary_codecs && enable_platform_hevc) {
sources += [
"h265_decoder.cc",
"h265_decoder.h",
"h265_dpb.cc",
"h265_dpb.h",
]
}
visibility = [
":gpu",
"//media/gpu/*",
......@@ -498,7 +507,9 @@ source_set("unit_tests") {
"//ui/gl:test_support",
]
sources = [ "h264_decoder_unittest.cc" ]
if (proprietary_codecs && enable_platform_hevc) {
sources += [ "h265_decoder_unittest.cc" ]
}
if (is_ash && (use_v4l2_codec || use_vaapi)) {
deps += [ "//media/gpu/chromeos:unit_tests" ]
}
......
// 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 <algorithm>
#include "base/logging.h"
#include "media/base/limits.h"
#include "media/gpu/h265_decoder.h"
namespace media {
H265Decoder::H265Accelerator::H265Accelerator() = default;
H265Decoder::H265Accelerator::~H265Accelerator() = default;
H265Decoder::H265Accelerator::Status H265Decoder::H265Accelerator::SetStream(
base::span<const uint8_t> stream,
const DecryptConfig* decrypt_config) {
return H265Decoder::H265Accelerator::Status::kNotSupported;
}
H265Decoder::H265Decoder(std::unique_ptr<H265Accelerator> accelerator,
VideoCodecProfile profile,
const VideoColorSpace& container_color_space)
: state_(kAfterReset),
container_color_space_(container_color_space),
profile_(profile),
accelerator_(std::move(accelerator)) {
DCHECK(accelerator_);
Reset();
}
H265Decoder::~H265Decoder() = default;
#define SET_ERROR_AND_RETURN() \
do { \
DVLOG(1) << "Error during decode"; \
state_ = kError; \
return H265Decoder::kDecodeError; \
} while (0)
#define CHECK_ACCELERATOR_RESULT(func) \
do { \
H265Accelerator::Status result = (func); \
switch (result) { \
case H265Accelerator::Status::kOk: \
break; \
case H265Accelerator::Status::kTryAgain: \
DVLOG(1) << #func " needs to try again"; \
return H265Decoder::kTryAgain; \
case H265Accelerator::Status::kFail: /* fallthrough */ \
case H265Accelerator::Status::kNotSupported: \
SET_ERROR_AND_RETURN(); \
} \
} while (0)
void H265Decoder::SetStream(int32_t id, const DecoderBuffer& decoder_buffer) {
const uint8_t* ptr = decoder_buffer.data();
const size_t size = decoder_buffer.data_size();
const DecryptConfig* decrypt_config = decoder_buffer.decrypt_config();
DCHECK(ptr);
DCHECK(size);
DVLOG(4) << "New input stream id: " << id << " at: " << (void*)ptr
<< " size: " << size;
stream_id_ = id;
current_stream_ = ptr;
current_stream_size_ = size;
current_stream_has_been_changed_ = true;
if (decrypt_config) {
parser_.SetEncryptedStream(ptr, size, decrypt_config->subsamples());
current_decrypt_config_ = decrypt_config->Clone();
} else {
parser_.SetStream(ptr, size);
current_decrypt_config_ = nullptr;
}
}
void H265Decoder::Reset() {
curr_nalu_ = nullptr;
parser_.Reset();
accelerator_->Reset();
state_ = kAfterReset;
}
H265Decoder::DecodeResult H265Decoder::Decode() {
if (state_ == kError) {
DVLOG(1) << "Decoder in error state";
return kDecodeError;
}
if (current_stream_has_been_changed_) {
// Calling H265Accelerator::SetStream() here instead of when the stream is
// originally set in case the accelerator needs to return kTryAgain.
H265Accelerator::Status result = accelerator_->SetStream(
base::span<const uint8_t>(current_stream_, current_stream_size_),
current_decrypt_config_.get());
switch (result) {
case H265Accelerator::Status::kOk: // fallthrough
case H265Accelerator::Status::kNotSupported:
// kNotSupported means the accelerator can't handle this stream,
// so everything will be done through the parser.
break;
case H265Accelerator::Status::kTryAgain:
DVLOG(1) << "SetStream() needs to try again";
return H265Decoder::kTryAgain;
case H265Accelerator::Status::kFail:
SET_ERROR_AND_RETURN();
}
// Reset the flag so that this is only called again next time SetStream()
// is called.
current_stream_has_been_changed_ = false;
}
while (true) {
H265Parser::Result par_res;
if (!curr_nalu_) {
curr_nalu_ = std::make_unique<H265NALU>();
par_res = parser_.AdvanceToNextNALU(curr_nalu_.get());
if (par_res == H265Parser::kEOStream) {
curr_nalu_.reset();
return kRanOutOfStreamData;
}
if (par_res != H265Parser::kOk) {
curr_nalu_.reset();
SET_ERROR_AND_RETURN();
}
DVLOG(4) << "New NALU: " << static_cast<int>(curr_nalu_->nal_unit_type);
}
// 8.1.2 We only want nuh_layer_id of zero.
if (curr_nalu_->nuh_layer_id) {
DVLOG(4) << "Skipping NALU with nuh_layer_id="
<< curr_nalu_->nuh_layer_id;
curr_nalu_.reset();
continue;
}
bool need_new_buffers;
switch (curr_nalu_->nal_unit_type) {
case H265NALU::SPS_NUT:
int sps_id;
par_res = parser_.ParseSPS(&sps_id);
if (par_res != H265Parser::kOk)
SET_ERROR_AND_RETURN();
break;
case H265NALU::PPS_NUT:
int pps_id;
par_res = parser_.ParsePPS(*curr_nalu_, &pps_id);
if (par_res != H265Parser::kOk)
SET_ERROR_AND_RETURN();
if (!ProcessPPS(pps_id, &need_new_buffers))
SET_ERROR_AND_RETURN();
if (need_new_buffers)
return kConfigChange;
break;
default:
DVLOG(4) << "Skipping NALU type: " << curr_nalu_->nal_unit_type;
break;
}
DVLOG(4) << "NALU done";
curr_nalu_.reset();
}
}
gfx::Size H265Decoder::GetPicSize() const {
return pic_size_;
}
gfx::Rect H265Decoder::GetVisibleRect() const {
return visible_rect_;
}
VideoCodecProfile H265Decoder::GetProfile() const {
return profile_;
}
size_t H265Decoder::GetRequiredNumOfPictures() const {
constexpr size_t kPicsInPipeline = limits::kMaxVideoFrames + 1;
return GetNumReferenceFrames() + kPicsInPipeline;
}
size_t H265Decoder::GetNumReferenceFrames() const {
// Use the maximum number of pictures in the Decoded Picture Buffer.
return dpb_.max_num_pics();
}
bool H265Decoder::ProcessPPS(int pps_id, bool* need_new_buffers) {
DVLOG(4) << "Processing PPS id:" << pps_id;
const H265PPS* pps = parser_.GetPPS(pps_id);
// Slice header parsing already verified this should exist.
DCHECK(pps);
const H265SPS* sps = parser_.GetSPS(pps->pps_seq_parameter_set_id);
// PPS parsing already verified this should exist.
DCHECK(sps);
if (need_new_buffers)
*need_new_buffers = false;
gfx::Size new_pic_size = sps->GetCodedSize();
gfx::Rect new_visible_rect = sps->GetVisibleRect();
if (visible_rect_ != new_visible_rect) {
DVLOG(2) << "New visible rect: " << new_visible_rect.ToString();
visible_rect_ = new_visible_rect;
}
// Equation 7-8
max_pic_order_cnt_lsb_ =
std::pow(2, sps->log2_max_pic_order_cnt_lsb_minus4 + 4);
VideoCodecProfile new_profile = H265Parser::ProfileIDCToVideoCodecProfile(
sps->profile_tier_level.general_profile_idc);
if (pic_size_ != new_pic_size || dpb_.max_num_pics() != sps->max_dpb_size ||
profile_ != new_profile) {
if (!Flush())
return false;
DVLOG(1) << "Codec profile: " << GetProfileName(new_profile)
<< ", level(x30): " << sps->profile_tier_level.general_level_idc
<< ", DPB size: " << sps->max_dpb_size
<< ", Picture size: " << new_pic_size.ToString();
profile_ = new_profile;
pic_size_ = new_pic_size;
dpb_.set_max_num_pics(sps->max_dpb_size);
if (need_new_buffers)
*need_new_buffers = true;
}
return true;
}
bool H265Decoder::Flush() {
DVLOG(2) << "Decoder flush";
return true;
}
} // 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_H265_DECODER_H_
#define MEDIA_GPU_H265_DECODER_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <vector>
#include "base/containers/span.h"
#include "media/base/decrypt_config.h"
#include "media/base/video_codecs.h"
#include "media/gpu/accelerated_video_decoder.h"
#include "media/gpu/h265_dpb.h"
#include "media/gpu/media_gpu_export.h"
#include "media/video/h265_parser.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace media {
// Clients of this class are expected to pass H265 Annex-B byte stream
// and are expected to provide an implementation of H265Accelerator 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.
//
// It is expected that when a DecoderBuffer is submitted, that it will contain a
// complete frame of data. Multiple slices per frame are handled. This class can
// also handle multiple frames in a DecoderBuffer, but that condition should
// never actually occur.
class MEDIA_GPU_EXPORT H265Decoder final : public AcceleratedVideoDecoder {
public:
class MEDIA_GPU_EXPORT H265Accelerator {
public:
// Methods may return kTryAgain if they need additional data (provided
// independently) in order to proceed. Examples are things like not having
// an appropriate key to decode encrypted content, or needing to wait
// until hardware buffers are available. This is not considered an
// unrecoverable error, but rather a pause to allow an application to
// independently provide the required data. When H265Decoder::Decode()
// is called again, it will attempt to resume processing of the stream
// by calling the same method again.
enum class Status {
// Operation completed successfully.
kOk,
// Operation failed.
kFail,
// Operation failed because some external data is missing. Retry the same
// operation later, once the data has been provided.
kTryAgain,
// Operation is not supported. Used by SetStream() to indicate that the
// Accelerator can not handle this operation.
kNotSupported,
};
H265Accelerator();
H265Accelerator(const H265Accelerator&) = delete;
H265Accelerator& operator=(const H265Accelerator&) = delete;
virtual ~H265Accelerator();
// 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;
// Notifies the accelerator whenever there is a new stream to process.
// |stream| is the data in annex B format, which may include SPS and PPS
// NALUs when there is a configuration change. The first frame must contain
// the SPS and PPS NALUs. SPS and PPS NALUs may not be encrypted.
// |decrypt_config| is the config for decrypting the stream. The accelerator
// should use |decrypt_config| to keep track of the parts of |stream| that
// are encrypted. If kTryAgain is returned, the decoder will retry this call
// later. This method has a default implementation that returns
// kNotSupported.
virtual Status SetStream(base::span<const uint8_t> stream,
const DecryptConfig* decrypt_config);
};
H265Decoder(std::unique_ptr<H265Accelerator> accelerator,
VideoCodecProfile profile,
const VideoColorSpace& container_color_space = VideoColorSpace());
H265Decoder(const H265Decoder&) = delete;
H265Decoder& operator=(const H265Decoder&) = delete;
~H265Decoder() override;
// AcceleratedVideoDecoder implementation.
void SetStream(int32_t id, const DecoderBuffer& decoder) 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:
// Internal state of the decoder.
enum State {
// Ready to decode from any point.
kDecoding,
// After Reset(), need a resume point.
kAfterReset,
// The following keep track of what step is next in Decode() processing
// in order to resume properly after H265Decoder::kTryAgain (or another
// retryable error) is returned. The next time Decode() is called the call
// that previously failed will be retried and execution continues from
// there (if possible).
kTryPreprocessCurrentSlice,
kEnsurePicture,
kTryNewFrame,
kTryCurrentSlice,
// Error in decode, can't continue.
kError,
};
// Process H265 stream structures.
bool ProcessPPS(int pps_id, bool* need_new_buffers);
// Decoder state.
State state_;
// The colorspace for the h265 container.
const VideoColorSpace container_color_space_;
// Parser in use.
H265Parser parser_;
// Most recent call to SetStream().
const uint8_t* current_stream_ = nullptr;
size_t current_stream_size_ = 0;
// Decrypting config for the most recent data passed to SetStream().
std::unique_ptr<DecryptConfig> current_decrypt_config_;
// Keep track of when SetStream() is called so that
// H265Accelerator::SetStream() can be called.
bool current_stream_has_been_changed_ = false;
// DPB in use.
H265DPB dpb_;
// Current stream buffer id; to be assigned to pictures decoded from it.
int32_t stream_id_ = -1;
// Global state values, needed in decoding. See spec.
int max_pic_order_cnt_lsb_;
// Current NALU being processed.
std::unique_ptr<H265NALU> curr_nalu_;
// Output picture size.
gfx::Size pic_size_;
// Output visible cropping rect.
gfx::Rect visible_rect_;
// Profile of input bitstream.
VideoCodecProfile profile_;
const std::unique_ptr<H265Accelerator> accelerator_;
};
} // namespace media
#endif // MEDIA_GPU_H265_DECODER_H_
\ No newline at end of file
// 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 <cstring>
#include <memory>
#include <string>
#include "base/check.h"
#include "base/containers/queue.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "media/base/test_data_util.h"
#include "media/gpu/h265_decoder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Args;
using ::testing::Expectation;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::MakeMatcher;
using ::testing::Matcher;
using ::testing::MatcherInterface;
using ::testing::MatchResultListener;
using ::testing::Mock;
using ::testing::Return;
using ::testing::WithArg;
namespace media {
namespace {
constexpr char kFrame0[] = "bear-frame0.hevc";
} // namespace
class MockH265Accelerator : public H265Decoder::H265Accelerator {
public:
MockH265Accelerator() = default;
MOCK_METHOD2(SetStream,
Status(base::span<const uint8_t> stream,
const DecryptConfig* decrypt_config));
void Reset() override {}
};
// Test H265Decoder by feeding different h265 frame sequences and make sure it
// behaves as expected.
class H265DecoderTest : public ::testing::Test {
public:
H265DecoderTest() = default;
void SetUp() override;
// Sets the bitstreams to be decoded, frame by frame. The content of each
// file is the encoded bitstream of a single video frame.
void SetInputFrameFiles(const std::vector<std::string>& frame_files);
// Keeps decoding the input bitstream set at |SetInputFrameFiles| until the
// decoder has consumed all bitstreams or returned from
// |H265Decoder::Decode|. Returns the same result as |H265Decoder::Decode|.
AcceleratedVideoDecoder::DecodeResult Decode();
protected:
std::unique_ptr<H265Decoder> decoder_;
MockH265Accelerator* accelerator_;
private:
base::queue<std::string> input_frame_files_;
std::string bitstream_;
scoped_refptr<DecoderBuffer> decoder_buffer_;
};
void H265DecoderTest::SetUp() {
auto mock_accelerator = std::make_unique<MockH265Accelerator>();
accelerator_ = mock_accelerator.get();
decoder_.reset(new H265Decoder(std::move(mock_accelerator),
VIDEO_CODEC_PROFILE_UNKNOWN));
// Sets default behaviors for mock methods for convenience.
ON_CALL(*accelerator_, SetStream(_, _))
.WillByDefault(
Return(H265Decoder::H265Accelerator::Status::kNotSupported));
}
void H265DecoderTest::SetInputFrameFiles(
const std::vector<std::string>& input_frame_files) {
for (auto f : input_frame_files)
input_frame_files_.push(f);
}
AcceleratedVideoDecoder::DecodeResult H265DecoderTest::Decode() {
while (true) {
auto result = decoder_->Decode();
int32_t bitstream_id = 0;
if (result != AcceleratedVideoDecoder::kRanOutOfStreamData ||
input_frame_files_.empty())
return result;
auto input_file = GetTestDataFilePath(input_frame_files_.front());
input_frame_files_.pop();
CHECK(base::ReadFileToString(input_file, &bitstream_));
decoder_buffer_ = DecoderBuffer::CopyFrom(
reinterpret_cast<const uint8_t*>(bitstream_.data()), bitstream_.size());
EXPECT_NE(decoder_buffer_.get(), nullptr);
decoder_->SetStream(bitstream_id++, *decoder_buffer_);
}
}
// Test Cases
TEST_F(H265DecoderTest, DecodeSingleFrame) {
SetInputFrameFiles({kFrame0});
EXPECT_EQ(AcceleratedVideoDecoder::kConfigChange, Decode());
EXPECT_EQ(gfx::Size(320, 184), decoder_->GetPicSize());
EXPECT_EQ(HEVCPROFILE_MAIN, decoder_->GetProfile());
EXPECT_EQ(17u, decoder_->GetRequiredNumOfPictures());
// TODO(jkardatzke): Test the rest of decoding.
}
} // 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 <algorithm>
#include "base/logging.h"
#include "media/gpu/h265_dpb.h"
#include "media/video/h265_parser.h"
namespace media {
H265DPB::H265DPB() : max_num_pics_(0) {}
H265DPB::~H265DPB() = default;
void H265DPB::set_max_num_pics(size_t max_num_pics) {
DCHECK_LE(max_num_pics, static_cast<size_t>(kMaxDpbSize));
max_num_pics_ = max_num_pics;
}
} // namespace media
\ No newline at end of file
// 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_H265_DPB_H_
#define MEDIA_GPU_H265_DPB_H_
namespace media {
// DPB - Decoded Picture Buffer.
// Stores decoded pictures that will be used for future display
// and/or reference.
class H265DPB {
public:
H265DPB();
H265DPB(const H265DPB&) = delete;
H265DPB& operator=(const H265DPB&) = delete;
~H265DPB();
void set_max_num_pics(size_t max_num_pics);
size_t max_num_pics() const { return max_num_pics_; }
private:
size_t max_num_pics_;
};
} // namespace media
#endif // MEDIA_GPU_H265_DPB_H_
\ No newline at end of file
......@@ -880,7 +880,7 @@ Created using "avconv -i bear-vp9.webm -vcodec copy -an -f ivf bear-vp9.ivf".
Manually dumped from libvpx with bear-vp9.ivf and test-25fps.vp9. See
vp9_parser_unittest.cc for description of their format.
### HEVC parser test files:
### HEVC parser/decoder test files:
#### bear.hevc
Used by h265_parser_unittest.cc.
......@@ -889,6 +889,9 @@ Used by h265_parser_unittest.cc.
Used by h265_parser_unittest.cc. Copied from bbb_hevc_176x144_176kbps_60fps.hevc
in Android repo.
#### bear-frame0.hevc
SPS, PPS and single IDR frame from bear.hevc for h265_decoder_unittest.cc.
### WebM files for testing multiple tracks.
#### green-a300hz.webm
......
......@@ -518,7 +518,8 @@ H265Parser::Result H265Parser::ParseSPS(int* sps_id) {
// Equation A-2: Calculate max_dpb_size.
int max_luma_ps = sps->profile_tier_level.GetMaxLumaPs();
int pic_size_in_samples_y = sps->pic_height_in_luma_samples;
int pic_size_in_samples_y =
sps->pic_height_in_luma_samples * sps->pic_width_in_luma_samples;
size_t max_dpb_pic_buf = sps->profile_tier_level.GetDpbMaxPicBuf();
if (pic_size_in_samples_y <= (max_luma_ps >> 2))
sps->max_dpb_size = std::min(4 * max_dpb_pic_buf, size_t{16});
......
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