Commit 762f6a2b authored by Jeffrey Kardatzke's avatar Jeffrey Kardatzke Committed by Commit Bot

Add H265 PPS parsing

BUG=chromium:1141237,b:153111783
TEST=media_unitests, media_h265_parser_fuzzer

Change-Id: I0be4bbb23add3c1a47d66a36cb331ae8776d0d66
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2503432
Commit-Queue: Jeffrey Kardatzke <jkardatzke@google.com>
Auto-Submit: 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@{#821811}
parent b9c180c8
......@@ -176,6 +176,10 @@ H265VUIParameters::H265VUIParameters() {
memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
}
H265PPS::H265PPS() {
memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
}
H265Parser::H265Parser() {
Reset();
}
......@@ -378,6 +382,7 @@ H265Parser::Result H265Parser::AdvanceToNextNALU(H265NALU* nalu) {
return kEOStream;
}
DCHECK(nalu);
nalu->data = stream_ + start_code_size;
nalu->size = nalu_size_with_start_code - start_code_size;
DVLOG(4) << "NALU found: size=" << nalu_size_with_start_code;
......@@ -414,6 +419,7 @@ H265Parser::Result H265Parser::ParseSPS(int* sps_id) {
DVLOG(4) << "Parsing SPS";
Result res = kOk;
DCHECK(sps_id);
*sps_id = -1;
std::unique_ptr<H265SPS> sps = std::make_unique<H265SPS>();
......@@ -692,6 +698,152 @@ H265Parser::Result H265Parser::ParseSPS(int* sps_id) {
return res;
}
H265Parser::Result H265Parser::ParsePPS(const H265NALU& nalu, int* pps_id) {
// 7.4.3.3
DVLOG(4) << "Parsing PPS";
Result res = kOk;
DCHECK(pps_id);
*pps_id = -1;
std::unique_ptr<H265PPS> pps = std::make_unique<H265PPS>();
pps->temporal_id = nalu.nuh_temporal_id_plus1 - 1;
// Set these defaults if they are not present here.
pps->loop_filter_across_tiles_enabled_flag = 1;
// 7.4.3.3.1
READ_UE_OR_RETURN(&pps->pps_pic_parameter_set_id);
IN_RANGE_OR_RETURN(pps->pps_pic_parameter_set_id, 0, 63);
READ_UE_OR_RETURN(&pps->pps_seq_parameter_set_id);
IN_RANGE_OR_RETURN(pps->pps_seq_parameter_set_id, 0, 15);
const H265SPS* sps = GetSPS(pps->pps_seq_parameter_set_id);
if (!sps) {
return kMissingParameterSet;
}
READ_BOOL_OR_RETURN(&pps->dependent_slice_segments_enabled_flag);
READ_BOOL_OR_RETURN(&pps->output_flag_present_flag);
READ_BITS_OR_RETURN(3, &pps->num_extra_slice_header_bits);
READ_BOOL_OR_RETURN(&pps->sign_data_hiding_enabled_flag);
READ_BOOL_OR_RETURN(&pps->cabac_init_present_flag);
READ_UE_OR_RETURN(&pps->num_ref_idx_l0_default_active_minus1);
IN_RANGE_OR_RETURN(pps->num_ref_idx_l0_default_active_minus1, 0, 14);
READ_UE_OR_RETURN(&pps->num_ref_idx_l1_default_active_minus1);
IN_RANGE_OR_RETURN(pps->num_ref_idx_l1_default_active_minus1, 0, 14);
READ_SE_OR_RETURN(&pps->init_qp_minus26);
pps->qp_bd_offset_y = 6 * sps->bit_depth_luma_minus8;
IN_RANGE_OR_RETURN(pps->init_qp_minus26, -(26 + pps->qp_bd_offset_y), 25);
READ_BOOL_OR_RETURN(&pps->constrained_intra_pred_flag);
READ_BOOL_OR_RETURN(&pps->transform_skip_enabled_flag);
READ_BOOL_OR_RETURN(&pps->cu_qp_delta_enabled_flag);
if (pps->cu_qp_delta_enabled_flag) {
READ_UE_OR_RETURN(&pps->diff_cu_qp_delta_depth);
IN_RANGE_OR_RETURN(pps->diff_cu_qp_delta_depth, 0,
sps->log2_diff_max_min_luma_coding_block_size);
}
READ_SE_OR_RETURN(&pps->pps_cb_qp_offset);
IN_RANGE_OR_RETURN(pps->pps_cb_qp_offset, -12, 12);
READ_SE_OR_RETURN(&pps->pps_cr_qp_offset);
IN_RANGE_OR_RETURN(pps->pps_cr_qp_offset, -12, 12);
READ_BOOL_OR_RETURN(&pps->pps_slice_chroma_qp_offsets_present_flag);
READ_BOOL_OR_RETURN(&pps->weighted_pred_flag);
READ_BOOL_OR_RETURN(&pps->weighted_bipred_flag);
READ_BOOL_OR_RETURN(&pps->transquant_bypass_enabled_flag);
READ_BOOL_OR_RETURN(&pps->tiles_enabled_flag);
READ_BOOL_OR_RETURN(&pps->entropy_coding_sync_enabled_flag);
if (pps->tiles_enabled_flag) {
READ_UE_OR_RETURN(&pps->num_tile_columns_minus1);
IN_RANGE_OR_RETURN(pps->num_tile_columns_minus1, 0,
sps->pic_width_in_ctbs_y - 1);
TRUE_OR_RETURN(pps->num_tile_columns_minus1 <
H265PPS::kMaxNumTileColumnWidth);
READ_UE_OR_RETURN(&pps->num_tile_rows_minus1);
IN_RANGE_OR_RETURN(pps->num_tile_rows_minus1, 0,
sps->pic_height_in_ctbs_y - 1);
TRUE_OR_RETURN((pps->num_tile_columns_minus1 != 0) ||
(pps->num_tile_rows_minus1 != 0));
TRUE_OR_RETURN(pps->num_tile_rows_minus1 < H265PPS::kMaxNumTileRowHeight);
READ_BOOL_OR_RETURN(&pps->uniform_spacing_flag);
if (!pps->uniform_spacing_flag) {
pps->column_width_minus1[pps->num_tile_columns_minus1] =
sps->pic_width_in_ctbs_y - 1;
for (int i = 0; i < pps->num_tile_columns_minus1; ++i) {
READ_UE_OR_RETURN(&pps->column_width_minus1[i]);
pps->column_width_minus1[pps->num_tile_columns_minus1] -=
pps->column_width_minus1[i] + 1;
}
pps->row_height_minus1[pps->num_tile_rows_minus1] =
sps->pic_height_in_ctbs_y - 1;
for (int i = 0; i < pps->num_tile_rows_minus1; ++i) {
READ_UE_OR_RETURN(&pps->row_height_minus1[i]);
pps->row_height_minus1[pps->num_tile_rows_minus1] -=
pps->row_height_minus1[i] + 1;
}
}
READ_BOOL_OR_RETURN(&pps->loop_filter_across_tiles_enabled_flag);
}
READ_BOOL_OR_RETURN(&pps->pps_loop_filter_across_slices_enabled_flag);
bool deblocking_filter_control_present_flag;
READ_BOOL_OR_RETURN(&deblocking_filter_control_present_flag);
if (deblocking_filter_control_present_flag) {
READ_BOOL_OR_RETURN(&pps->deblocking_filter_override_enabled_flag);
READ_BOOL_OR_RETURN(&pps->pps_deblocking_filter_disabled_flag);
if (!pps->pps_deblocking_filter_disabled_flag) {
READ_SE_OR_RETURN(&pps->pps_beta_offset_div2);
IN_RANGE_OR_RETURN(pps->pps_beta_offset_div2, -6, 6);
READ_SE_OR_RETURN(&pps->pps_tc_offset_div2);
IN_RANGE_OR_RETURN(pps->pps_tc_offset_div2, -6, 6);
}
}
READ_BOOL_OR_RETURN(&pps->pps_scaling_list_data_present_flag);
if (pps->pps_scaling_list_data_present_flag) {
res = ParseScalingListData(&pps->scaling_list_data);
if (res != kOk)
return res;
}
READ_BOOL_OR_RETURN(&pps->lists_modification_present_flag);
READ_UE_OR_RETURN(&pps->log2_parallel_merge_level_minus2);
IN_RANGE_OR_RETURN(pps->log2_parallel_merge_level_minus2, 0,
sps->ctb_log2_size_y - 2);
READ_BOOL_OR_RETURN(&pps->slice_segment_header_extension_present_flag);
bool pps_extension_present_flag;
READ_BOOL_OR_RETURN(&pps_extension_present_flag);
bool pps_range_extension_flag = false;
bool pps_multilayer_extension_flag = false;
bool pps_3d_extension_flag = false;
bool pps_scc_extension_flag = false;
if (pps_extension_present_flag) {
READ_BOOL_OR_RETURN(&pps_range_extension_flag);
READ_BOOL_OR_RETURN(&pps_multilayer_extension_flag);
READ_BOOL_OR_RETURN(&pps_3d_extension_flag);
READ_BOOL_OR_RETURN(&pps_scc_extension_flag);
SKIP_BITS_OR_RETURN(4); // pps_extension_4bits
}
if (pps_range_extension_flag) {
DVLOG(1) << "HEVC range extension not supported";
return kInvalidStream;
}
if (pps_multilayer_extension_flag) {
DVLOG(1) << "HEVC multilayer extension not supported";
return kInvalidStream;
}
if (pps_3d_extension_flag) {
DVLOG(1) << "HEVC 3D extension not supported";
return kInvalidStream;
}
if (pps_scc_extension_flag) {
DVLOG(1) << "HEVC SCC extension not supported";
return kInvalidStream;
}
// If a PPS with the same id already exists, replace it.
*pps_id = pps->pps_pic_parameter_set_id;
active_pps_[*pps_id] = std::move(pps);
return res;
}
const H265SPS* H265Parser::GetSPS(int sps_id) const {
auto it = active_sps_.find(sps_id);
if (it == active_sps_.end()) {
......@@ -702,6 +854,16 @@ const H265SPS* H265Parser::GetSPS(int sps_id) const {
return it->second.get();
}
const H265PPS* H265Parser::GetPPS(int pps_id) const {
auto it = active_pps_.find(pps_id);
if (it == active_pps_.end()) {
DVLOG(1) << "Requested a nonexistent PPS id " << pps_id;
return nullptr;
}
return it->second.get();
}
// static
VideoCodecProfile H265Parser::ProfileIDCToVideoCodecProfile(int profile_idc) {
switch (profile_idc) {
......
......@@ -265,14 +265,69 @@ struct MEDIA_EXPORT H265SPS {
VideoColorSpace GetColorSpace() const;
};
struct MEDIA_EXPORT H265PPS {
H265PPS();
enum {
kMaxNumTileColumnWidth = 19, // From VAAPI.
kMaxNumTileRowHeight = 21, // From VAAPI.
};
int temporal_id; // calculated from NALU
// Syntax elements.
int pps_pic_parameter_set_id;
int pps_seq_parameter_set_id;
bool dependent_slice_segments_enabled_flag;
bool output_flag_present_flag;
int num_extra_slice_header_bits;
bool sign_data_hiding_enabled_flag;
bool cabac_init_present_flag;
int num_ref_idx_l0_default_active_minus1;
int num_ref_idx_l1_default_active_minus1;
int init_qp_minus26;
bool constrained_intra_pred_flag;
bool transform_skip_enabled_flag;
bool cu_qp_delta_enabled_flag;
int diff_cu_qp_delta_depth;
int pps_cb_qp_offset;
int pps_cr_qp_offset;
bool pps_slice_chroma_qp_offsets_present_flag;
bool weighted_pred_flag;
bool weighted_bipred_flag;
bool transquant_bypass_enabled_flag;
bool tiles_enabled_flag;
bool entropy_coding_sync_enabled_flag;
int num_tile_columns_minus1;
int num_tile_rows_minus1;
bool uniform_spacing_flag;
int column_width_minus1[kMaxNumTileColumnWidth];
int row_height_minus1[kMaxNumTileRowHeight];
bool loop_filter_across_tiles_enabled_flag;
bool pps_loop_filter_across_slices_enabled_flag;
bool deblocking_filter_override_enabled_flag;
bool pps_deblocking_filter_disabled_flag;
int pps_beta_offset_div2;
int pps_tc_offset_div2;
bool pps_scaling_list_data_present_flag;
H265ScalingListData scaling_list_data;
bool lists_modification_present_flag;
int log2_parallel_merge_level_minus2;
bool slice_segment_header_extension_present_flag;
// Calculated fields.
int qp_bd_offset_y;
};
// Class to parse an Annex-B H.265 stream.
class MEDIA_EXPORT H265Parser {
public:
enum Result {
kOk,
kInvalidStream, // error in stream
kUnsupportedStream, // stream not supported by the parser
kEOStream, // end of stream
kInvalidStream, // error in stream
kUnsupportedStream, // stream not supported by the parser
kMissingParameterSet, // missing PPS/SPS from what was parsed
kEOStream, // end of stream
};
H265Parser();
......@@ -299,16 +354,21 @@ class MEDIA_EXPORT H265Parser {
// NALU-specific parsing functions.
// These should be called after AdvanceToNextNALU().
// SPSes are owned by the parser class and the memory for their structures is
// managed here, not by the caller, as they are reused across NALUs.
// SPSes and PPSes are owned by the parser class and the memory for their
// structures is managed here, not by the caller, as they are reused across
// NALUs.
//
// Parse an SPS NALU and save their data in the parser, returning id of the
// parsed structure in |*sps_id|. To get a pointer to a given SPS structure,
// use GetSPS(), passing the returned |*sps_id| as parameter.
// Parse an SPS/PPS NALU and save their data in the parser, returning id
// of the parsed structure in |*pps_id|/|*sps_id|. To get a pointer to a given
// SPS/PPS structure, use GetSPS()/GetPPS(), passing the returned
// |*sps_id|/|*pps_id| as parameter.
Result ParseSPS(int* sps_id);
Result ParsePPS(const H265NALU& nalu, int* pps_id);
// Return a pointer to SPS with given |sps_id| or null if not present.
// Return a pointer to SPS/PPS with given |sps_id|/|pps_id| or null if not
// present.
const H265SPS* GetSPS(int sps_id) const;
const H265PPS* GetPPS(int pps_id) const;
static VideoCodecProfile ProfileIDCToVideoCodecProfile(int profile_idc);
......@@ -351,8 +411,9 @@ class MEDIA_EXPORT H265Parser {
H264BitReader br_;
// SPSes stored for future reference.
// PPSes and SPSes stored for future reference.
base::flat_map<int, std::unique_ptr<H265SPS>> active_sps_;
base::flat_map<int, std::unique_ptr<H265PPS>> active_pps_;
// Ranges of encrypted bytes in the buffer passed to SetEncryptedStream().
Ranges<const uint8_t*> encrypted_ranges_;
......
......@@ -31,6 +31,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
int sps_id;
res = parser.ParseSPS(&sps_id);
break;
case media::H265NALU::PPS_NUT:
int pps_id;
res = parser.ParsePPS(nalu, &pps_id);
break;
default:
// Skip any other NALU.
break;
......
......@@ -36,15 +36,14 @@ class H265ParserTest : public ::testing::Test {
parser_.SetStream(stream_->data(), stream_->length());
}
bool ParseNalusUntilNut(H265NALU::Type nalu_type) {
bool ParseNalusUntilNut(H265NALU* target_nalu, H265NALU::Type nalu_type) {
while (true) {
H265NALU nalu;
H265Parser::Result res = parser_.AdvanceToNextNALU(&nalu);
H265Parser::Result res = parser_.AdvanceToNextNALU(target_nalu);
if (res == H265Parser::kEOStream) {
return false;
}
EXPECT_EQ(res, H265Parser::kOk);
if (nalu.nal_unit_type == nalu_type)
if (target_nalu->nal_unit_type == nalu_type)
return true;
}
}
......@@ -84,6 +83,11 @@ TEST_F(H265ParserTest, RawHevcStreamFileParsing) {
res = parser_.ParseSPS(&sps_id);
EXPECT_TRUE(!!parser_.GetSPS(sps_id));
break;
case H265NALU::PPS_NUT:
int pps_id;
res = parser_.ParsePPS(nalu, &pps_id);
EXPECT_TRUE(!!parser_.GetPPS(pps_id));
break;
default:
break;
}
......@@ -94,7 +98,8 @@ TEST_F(H265ParserTest, RawHevcStreamFileParsing) {
TEST_F(H265ParserTest, SpsParsing) {
LoadParserFile("bear.hevc");
EXPECT_TRUE(ParseNalusUntilNut(H265NALU::SPS_NUT));
H265NALU target_nalu;
EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H265NALU::SPS_NUT));
int sps_id;
EXPECT_EQ(H265Parser::kOk, parser_.ParseSPS(&sps_id));
const H265SPS* sps = parser_.GetSPS(sps_id);
......@@ -156,4 +161,45 @@ TEST_F(H265ParserTest, SpsParsing) {
EXPECT_EQ(sps->vui_parameters.def_disp_win_bottom_offset, 0);
}
TEST_F(H265ParserTest, PpsParsing) {
LoadParserFile("bear.hevc");
H265NALU target_nalu;
EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H265NALU::SPS_NUT));
int sps_id;
// We need to parse the SPS so the PPS can find it.
EXPECT_EQ(H265Parser::kOk, parser_.ParseSPS(&sps_id));
EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H265NALU::PPS_NUT));
int pps_id;
EXPECT_EQ(H265Parser::kOk, parser_.ParsePPS(target_nalu, &pps_id));
const H265PPS* pps = parser_.GetPPS(pps_id);
EXPECT_TRUE(!!pps);
EXPECT_EQ(pps->pps_pic_parameter_set_id, 0);
EXPECT_EQ(pps->pps_seq_parameter_set_id, 0);
EXPECT_FALSE(pps->dependent_slice_segments_enabled_flag);
EXPECT_FALSE(pps->output_flag_present_flag);
EXPECT_EQ(pps->num_extra_slice_header_bits, 0);
EXPECT_TRUE(pps->sign_data_hiding_enabled_flag);
EXPECT_FALSE(pps->cabac_init_present_flag);
EXPECT_EQ(pps->num_ref_idx_l0_default_active_minus1, 0);
EXPECT_EQ(pps->num_ref_idx_l1_default_active_minus1, 0);
EXPECT_EQ(pps->init_qp_minus26, 0);
EXPECT_FALSE(pps->constrained_intra_pred_flag);
EXPECT_FALSE(pps->transform_skip_enabled_flag);
EXPECT_TRUE(pps->cu_qp_delta_enabled_flag);
EXPECT_EQ(pps->diff_cu_qp_delta_depth, 0);
EXPECT_EQ(pps->pps_cb_qp_offset, 0);
EXPECT_EQ(pps->pps_cr_qp_offset, 0);
EXPECT_FALSE(pps->pps_slice_chroma_qp_offsets_present_flag);
EXPECT_TRUE(pps->weighted_pred_flag);
EXPECT_FALSE(pps->weighted_bipred_flag);
EXPECT_FALSE(pps->transquant_bypass_enabled_flag);
EXPECT_FALSE(pps->tiles_enabled_flag);
EXPECT_TRUE(pps->entropy_coding_sync_enabled_flag);
EXPECT_TRUE(pps->loop_filter_across_tiles_enabled_flag);
EXPECT_FALSE(pps->pps_scaling_list_data_present_flag);
EXPECT_FALSE(pps->lists_modification_present_flag);
EXPECT_EQ(pps->log2_parallel_merge_level_minus2, 0);
EXPECT_FALSE(pps->slice_segment_header_extension_present_flag);
}
} // namespace media
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