Bug: webrtc:342905193 No-Try: True Change-Id: Icc968be43b8830038ea9a1f5f604307220457807 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/361021 Auto-Submit: Florent Castelli <orphis@webrtc.org> Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Florent Castelli <orphis@webrtc.org> Cr-Commit-Position: refs/heads/main@{#42911}
536 lines
18 KiB
C++
536 lines
18 KiB
C++
/*
|
|
* Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "common_video/h265/h265_sps_parser.h"
|
|
|
|
#include "common_video/h265/h265_common.h"
|
|
#include "rtc_base/arraysize.h"
|
|
#include "rtc_base/bit_buffer.h"
|
|
#include "rtc_base/buffer.h"
|
|
#include "test/gtest.h"
|
|
|
|
namespace webrtc {
|
|
|
|
static constexpr size_t kSpsBufferMaxSize = 256;
|
|
|
|
// Generates a fake SPS with basically everything empty but the width/height,
|
|
// max_num_sublayer_minus1 and num_short_term_ref_pic_sets.
|
|
// Pass in a buffer of at least kSpsBufferMaxSize.
|
|
// The fake SPS that this generates also always has at least one emulation byte
|
|
// at offset 2, since the first two bytes are always 0, and has a 0x3 as the
|
|
// level_idc, to make sure the parser doesn't eat all 0x3 bytes.
|
|
// num_short_term_ref_pic_sets is set to 11 followed with 11
|
|
// short_term_ref_pic_set data in this fake sps.
|
|
void WriteSps(uint16_t width,
|
|
uint16_t height,
|
|
int id,
|
|
uint32_t max_num_sublayer_minus1,
|
|
bool sub_layer_ordering_info_present_flag,
|
|
bool long_term_ref_pics_present_flag,
|
|
rtc::Buffer* out_buffer) {
|
|
uint8_t rbsp[kSpsBufferMaxSize] = {0};
|
|
rtc::BitBufferWriter writer(rbsp, kSpsBufferMaxSize);
|
|
// sps_video_parameter_set_id
|
|
writer.WriteBits(0, 4);
|
|
// sps_max_sub_layers_minus1
|
|
writer.WriteBits(max_num_sublayer_minus1, 3);
|
|
// sps_temporal_id_nesting_flag
|
|
writer.WriteBits(1, 1);
|
|
// profile_tier_level(profilePresentFlag=1, maxNumSublayersMinus1=0)
|
|
// profile-space=0, tier=0, profile-idc=1
|
|
writer.WriteBits(0, 2);
|
|
writer.WriteBits(0, 1);
|
|
writer.WriteBits(1, 5);
|
|
// general_prfile_compatibility_flag[32]
|
|
writer.WriteBits(0, 32);
|
|
// general_progressive_source_flag
|
|
writer.WriteBits(1, 1);
|
|
// general_interlace_source_flag
|
|
writer.WriteBits(0, 1);
|
|
// general_non_packed_constraint_flag
|
|
writer.WriteBits(0, 1);
|
|
// general_frame_only_constraint_flag
|
|
writer.WriteBits(1, 1);
|
|
// general_reserved_zero_7bits
|
|
writer.WriteBits(0, 7);
|
|
// general_one_picture_only_flag
|
|
writer.WriteBits(0, 1);
|
|
// general_reserved_zero_35bits
|
|
writer.WriteBits(0, 35);
|
|
// general_inbld_flag
|
|
writer.WriteBits(0, 1);
|
|
// general_level_idc
|
|
writer.WriteBits(93, 8);
|
|
// if max_sub_layers_minus1 >=1, read the sublayer profile information
|
|
std::vector<uint32_t> sub_layer_profile_present_flags;
|
|
std::vector<uint32_t> sub_layer_level_present_flags;
|
|
for (uint32_t i = 0; i < max_num_sublayer_minus1; i++) {
|
|
// sublayer_profile_present_flag and sublayer_level_presnet_flag: u(2)
|
|
writer.WriteBits(1, 1);
|
|
writer.WriteBits(1, 1);
|
|
sub_layer_profile_present_flags.push_back(1);
|
|
sub_layer_level_present_flags.push_back(1);
|
|
}
|
|
if (max_num_sublayer_minus1 > 0) {
|
|
for (uint32_t j = max_num_sublayer_minus1; j < 8; j++) {
|
|
// reserved 2 bits: u(2)
|
|
writer.WriteBits(0, 2);
|
|
}
|
|
}
|
|
for (uint32_t k = 0; k < max_num_sublayer_minus1; k++) {
|
|
if (sub_layer_profile_present_flags[k]) { //
|
|
// sub_layer profile_space/tier_flag/profile_idc. ignored. u(8)
|
|
writer.WriteBits(0, 8);
|
|
// profile_compatability_flag: u(32)
|
|
writer.WriteBits(0, 32);
|
|
// sub_layer progressive_source_flag/interlaced_source_flag/
|
|
// non_packed_constraint_flag/frame_only_constraint_flag: u(4)
|
|
writer.WriteBits(0, 4);
|
|
// following 43-bits are profile_idc specific. We simply read/skip it.
|
|
// u(43)
|
|
writer.WriteBits(0, 43);
|
|
// 1-bit profile_idc specific inbld flag. We simply read/skip it. u(1)
|
|
writer.WriteBits(0, 1);
|
|
}
|
|
if (sub_layer_level_present_flags[k]) {
|
|
// sub_layer_level_idc: u(8)
|
|
writer.WriteBits(0, 8);
|
|
}
|
|
}
|
|
|
|
// seq_parameter_set_id
|
|
writer.WriteExponentialGolomb(id);
|
|
// chroma_format_idc
|
|
writer.WriteExponentialGolomb(2);
|
|
if (width % 8 != 0 || height % 8 != 0) {
|
|
int width_delta = 8 - width % 8;
|
|
int height_delta = 8 - height % 8;
|
|
if (width_delta != 8) {
|
|
// pic_width_in_luma_samples
|
|
writer.WriteExponentialGolomb(width + width_delta);
|
|
} else {
|
|
writer.WriteExponentialGolomb(width);
|
|
}
|
|
if (height_delta != 8) {
|
|
// pic_height_in_luma_samples
|
|
writer.WriteExponentialGolomb(height + height_delta);
|
|
} else {
|
|
writer.WriteExponentialGolomb(height);
|
|
}
|
|
// conformance_window_flag
|
|
writer.WriteBits(1, 1);
|
|
// conf_win_left_offset
|
|
writer.WriteExponentialGolomb((width % 8) / 2);
|
|
// conf_win_right_offset
|
|
writer.WriteExponentialGolomb(0);
|
|
// conf_win_top_offset
|
|
writer.WriteExponentialGolomb(height_delta);
|
|
// conf_win_bottom_offset
|
|
writer.WriteExponentialGolomb(0);
|
|
} else {
|
|
// pic_width_in_luma_samples
|
|
writer.WriteExponentialGolomb(width);
|
|
// pic_height_in_luma_samples
|
|
writer.WriteExponentialGolomb(height);
|
|
// conformance_window_flag
|
|
writer.WriteBits(0, 1);
|
|
}
|
|
// bit_depth_luma_minus8
|
|
writer.WriteExponentialGolomb(0);
|
|
// bit_depth_chroma_minus8
|
|
writer.WriteExponentialGolomb(0);
|
|
// log2_max_pic_order_cnt_lsb_minus4
|
|
writer.WriteExponentialGolomb(4);
|
|
// sps_sub_layer_ordering_info_present_flag
|
|
writer.WriteBits(sub_layer_ordering_info_present_flag, 1);
|
|
for (uint32_t i = (sub_layer_ordering_info_present_flag != 0)
|
|
? 0
|
|
: max_num_sublayer_minus1;
|
|
i <= max_num_sublayer_minus1; i++) {
|
|
// sps_max_dec_pic_buffering_minus1: ue(v)
|
|
writer.WriteExponentialGolomb(4);
|
|
// sps_max_num_reorder_pics: ue(v)
|
|
writer.WriteExponentialGolomb(3);
|
|
// sps_max_latency_increase_plus1: ue(v)
|
|
writer.WriteExponentialGolomb(0);
|
|
}
|
|
// log2_min_luma_coding_block_size_minus3
|
|
writer.WriteExponentialGolomb(0);
|
|
// log2_diff_max_min_luma_coding_block_size
|
|
writer.WriteExponentialGolomb(3);
|
|
// log2_min_luma_transform_block_size_minus2
|
|
writer.WriteExponentialGolomb(0);
|
|
// log2_diff_max_min_luma_transform_block_size
|
|
writer.WriteExponentialGolomb(3);
|
|
// max_transform_hierarchy_depth_inter
|
|
writer.WriteExponentialGolomb(0);
|
|
// max_transform_hierarchy_depth_intra
|
|
writer.WriteExponentialGolomb(0);
|
|
// scaling_list_enabled_flag
|
|
writer.WriteBits(0, 1);
|
|
// apm_enabled_flag
|
|
writer.WriteBits(0, 1);
|
|
// sample_adaptive_offset_enabled_flag
|
|
writer.WriteBits(1, 1);
|
|
// pcm_enabled_flag
|
|
writer.WriteBits(0, 1);
|
|
// num_short_term_ref_pic_sets
|
|
writer.WriteExponentialGolomb(11);
|
|
// short_term_ref_pic_set[0]
|
|
// num_negative_pics
|
|
writer.WriteExponentialGolomb(4);
|
|
// num_positive_pics
|
|
writer.WriteExponentialGolomb(0);
|
|
// delta_poc_s0_minus1
|
|
writer.WriteExponentialGolomb(7);
|
|
// used_by_curr_pic_s0_flag
|
|
writer.WriteBits(1, 1);
|
|
for (int i = 0; i < 2; i++) {
|
|
// delta_poc_s0_minus1
|
|
writer.WriteExponentialGolomb(1);
|
|
// used_by_curr_pic_s0_flag
|
|
writer.WriteBits(1, 1);
|
|
}
|
|
// delta_poc_s0_minus1
|
|
writer.WriteExponentialGolomb(3);
|
|
// used_by_curr_pic_s0_flag
|
|
writer.WriteBits(1, 1);
|
|
// short_term_ref_pic_set[1]
|
|
// inter_ref_pic_set_prediction_flag
|
|
writer.WriteBits(1, 1);
|
|
// delta_rps_sign
|
|
writer.WriteBits(0, 1);
|
|
// abs_delta_rps_minus1
|
|
writer.WriteExponentialGolomb(3);
|
|
for (int i = 0; i < 2; i++) {
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
}
|
|
for (int i = 0; i < 2; i++) {
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(0, 1);
|
|
// use_delta_flag
|
|
writer.WriteBits(0, 1);
|
|
}
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
// short_term_ref_pic_set[2]
|
|
// inter_ref_pic_set_prediction_flag
|
|
writer.WriteBits(1, 1);
|
|
// delta_rps_sign
|
|
writer.WriteBits(0, 1);
|
|
// abs_delta_rps_minus1
|
|
writer.WriteExponentialGolomb(1);
|
|
for (int i = 0; i < 4; i++) {
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
}
|
|
// short_term_ref_pic_set[3]
|
|
// inter_ref_pic_set_prediction_flag
|
|
writer.WriteBits(1, 1);
|
|
// delta_rps_sign
|
|
writer.WriteBits(0, 1);
|
|
// abs_delta_rps_minus1
|
|
writer.WriteExponentialGolomb(0);
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(0, 1);
|
|
// use_delta_flag
|
|
writer.WriteBits(0, 1);
|
|
for (int i = 0; i < 3; i++) {
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
}
|
|
// short_term_ref_pic_set[4]
|
|
// inter_ref_pic_set_prediction_flag
|
|
writer.WriteBits(1, 1);
|
|
// delta_rps_sign
|
|
writer.WriteBits(1, 1);
|
|
// abs_delta_rps_minus1
|
|
writer.WriteExponentialGolomb(1);
|
|
for (int i = 0; i < 4; i++) {
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
}
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(0, 1);
|
|
// use_delta_flag
|
|
writer.WriteBits(0, 1);
|
|
// short_term_ref_pic_set[5]
|
|
// inter_ref_pic_set_prediction_flag
|
|
writer.WriteBits(1, 1);
|
|
// delta_rps_sign
|
|
writer.WriteBits(1, 1);
|
|
// abs_delta_rps_minus1
|
|
writer.WriteExponentialGolomb(2);
|
|
for (int i = 0; i < 4; i++) {
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
}
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(0, 1);
|
|
// use_delta_flag
|
|
writer.WriteBits(0, 1);
|
|
// short_term_ref_pic_set[6]
|
|
// inter_ref_pic_set_prediction_flag
|
|
writer.WriteBits(1, 1);
|
|
// delta_rps_sign
|
|
writer.WriteBits(0, 1);
|
|
// abs_delta_rps_minus1
|
|
writer.WriteExponentialGolomb(0);
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(0, 1);
|
|
// use_delta_flag
|
|
writer.WriteBits(0, 1);
|
|
for (int i = 0; i < 3; i++) {
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
}
|
|
// short_term_ref_pic_set[7]
|
|
// inter_ref_pic_set_prediction_flag
|
|
writer.WriteBits(1, 1);
|
|
// delta_rps_sign
|
|
writer.WriteBits(1, 1);
|
|
// abs_delta_rps_minus1
|
|
writer.WriteExponentialGolomb(1);
|
|
for (int i = 0; i < 4; i++) {
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
}
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(0, 1);
|
|
// use_delta_flag
|
|
writer.WriteBits(0, 1);
|
|
// short_term_ref_pic_set[8]
|
|
// inter_ref_pic_set_prediction_flag
|
|
writer.WriteBits(0, 1);
|
|
// num_negative_pics
|
|
writer.WriteExponentialGolomb(1);
|
|
// num_positive_pics
|
|
writer.WriteExponentialGolomb(0);
|
|
// delta_poc_s0_minus1
|
|
writer.WriteExponentialGolomb(7);
|
|
// used_by_curr_pic_s0_flag
|
|
writer.WriteBits(1, 1);
|
|
// short_term_ref_pic_set[9]
|
|
// inter_ref_pic_set_prediction_flag
|
|
writer.WriteBits(1, 1);
|
|
// delta_rps_sign
|
|
writer.WriteBits(0, 1);
|
|
// abs_delta_rps_minus1
|
|
writer.WriteExponentialGolomb(3);
|
|
for (int i = 0; i < 2; i++) {
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
}
|
|
// short_term_ref_pic_set[10]
|
|
// inter_ref_pic_set_prediction_flag
|
|
writer.WriteBits(1, 1);
|
|
// delta_rps_sign
|
|
writer.WriteBits(0, 1);
|
|
// abs_delta_rps_minus1
|
|
writer.WriteExponentialGolomb(1);
|
|
for (int i = 0; i < 3; i++) {
|
|
// used_by_curr_pic_flag
|
|
writer.WriteBits(1, 1);
|
|
}
|
|
// long_term_ref_pics_present_flag
|
|
writer.WriteBits(long_term_ref_pics_present_flag, 1);
|
|
if (long_term_ref_pics_present_flag) {
|
|
// num_long_term_ref_pics_sps
|
|
writer.WriteExponentialGolomb(1);
|
|
// lt_ref_pic_poc_lsb_sps
|
|
writer.WriteExponentialGolomb(1);
|
|
// used_by_curr_pic_lt_sps_flag
|
|
writer.WriteBits(1, 8);
|
|
}
|
|
// sps_temproal_mvp_enabled_flag
|
|
writer.WriteBits(1, 1);
|
|
|
|
// Get the number of bytes written (including the last partial byte).
|
|
size_t byte_count, bit_offset;
|
|
writer.GetCurrentOffset(&byte_count, &bit_offset);
|
|
if (bit_offset > 0) {
|
|
byte_count++;
|
|
}
|
|
|
|
out_buffer->Clear();
|
|
H265::WriteRbsp(rtc::MakeArrayView(rbsp, byte_count), out_buffer);
|
|
}
|
|
|
|
class H265SpsParserTest : public ::testing::Test {
|
|
public:
|
|
H265SpsParserTest() {}
|
|
~H265SpsParserTest() override {}
|
|
};
|
|
|
|
TEST_F(H265SpsParserTest, TestSampleSPSHdLandscape) {
|
|
// SPS for a 1280x720 camera capture from ffmpeg on linux. Contains
|
|
// emulation bytes but no cropping. This buffer is generated
|
|
// with following command:
|
|
// 1) ffmpeg -i /dev/video0 -r 30 -c:v libx265 -s 1280x720 camera.h265
|
|
//
|
|
// 2) Open camera.h265 and find the SPS, generally everything between the
|
|
// second and third start codes (0 0 0 1 or 0 0 1). The first two bytes should
|
|
// be 0x42 and 0x01, which should be stripped out before being passed to the
|
|
// parser.
|
|
const uint8_t buffer[] = {0x01, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x9d,
|
|
0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x5d, 0xb0,
|
|
0x02, 0x80, 0x80, 0x2d, 0x16, 0x59, 0x59, 0xa4,
|
|
0x93, 0x2b, 0x80, 0x40, 0x00, 0x00, 0x03, 0x00,
|
|
0x40, 0x00, 0x00, 0x07, 0x82};
|
|
std::optional<H265SpsParser::SpsState> sps = H265SpsParser::ParseSps(buffer);
|
|
ASSERT_TRUE(sps.has_value());
|
|
EXPECT_EQ(1280u, sps->width);
|
|
EXPECT_EQ(720u, sps->height);
|
|
}
|
|
|
|
TEST_F(H265SpsParserTest, TestSampleSPSVerticalCropLandscape) {
|
|
// SPS for a 640x260 camera captureH265SpsParser::ParseSps(buffer.data(),
|
|
// buffer.size()) from ffmpeg on Linux,. Contains emulation bytes and vertical
|
|
// cropping (crop from 640x264). The buffer is generated
|
|
// with following command:
|
|
// 1) Generate a video, from the camera:
|
|
// ffmpeg -i /dev/video0 -r 30 -c:v libx265 -s 640x264 camera.h265
|
|
//
|
|
// 2) Crop the video to expected size(for example, 640x260 which will crop
|
|
// from 640x264):
|
|
// ffmpeg -i camera.h265 -filter:v crop=640:260:200:200 -c:v libx265
|
|
// cropped.h265
|
|
//
|
|
// 3) Open cropped.h265 and find the SPS, generally everything between the
|
|
// second and third start codes (0 0 0 1 or 0 0 1). The first two bytes should
|
|
// be 0x42 and 0x01, which should be stripped out before being passed to the
|
|
// parser.
|
|
const uint8_t buffer[] = {0x01, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x9d,
|
|
0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x3f, 0xb0,
|
|
0x05, 0x02, 0x01, 0x09, 0xf2, 0xe5, 0x95, 0x9a,
|
|
0x49, 0x32, 0xb8, 0x04, 0x00, 0x00, 0x03, 0x00,
|
|
0x04, 0x00, 0x00, 0x03, 0x00, 0x78, 0x20};
|
|
std::optional<H265SpsParser::SpsState> sps = H265SpsParser::ParseSps(buffer);
|
|
ASSERT_TRUE(sps.has_value());
|
|
EXPECT_EQ(640u, sps->width);
|
|
EXPECT_EQ(260u, sps->height);
|
|
}
|
|
|
|
TEST_F(H265SpsParserTest, TestSampleSPSHorizontalAndVerticalCrop) {
|
|
// SPS for a 260x260 camera capture from ffmpeg on Linux. Contains emulation
|
|
// bytes. Horizontal and veritcal crop (Crop from 264x264). The buffer is
|
|
// generated with following command:
|
|
// 1) Generate a video, from the camera:
|
|
// ffmpeg -i /dev/video0 -r 30 -c:v libx265 -s 264x264 camera.h265
|
|
//
|
|
// 2) Crop the video to expected size(for example, 260x260 which will crop
|
|
// from 264x264):
|
|
// ffmpeg -i camera.h265 -filter:v crop=260:260:200:200 -c:v libx265
|
|
// cropped.h265
|
|
//
|
|
// 3) Open cropped.h265 and find the SPS, generally everything between the
|
|
// second and third start codes (0 0 0 1 or 0 0 1). The first two bytes should
|
|
// be 0x42 and 0x01, which should be stripped out before being passed to the
|
|
// parser.
|
|
const uint8_t buffer[] = {0x01, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x9d,
|
|
0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x3c, 0xb0,
|
|
0x08, 0x48, 0x04, 0x27, 0x72, 0xe5, 0x95, 0x9a,
|
|
0x49, 0x32, 0xb8, 0x04, 0x00, 0x00, 0x03, 0x00,
|
|
0x04, 0x00, 0x00, 0x03, 0x00, 0x78, 0x20};
|
|
std::optional<H265SpsParser::SpsState> sps = H265SpsParser::ParseSps(buffer);
|
|
ASSERT_TRUE(sps.has_value());
|
|
EXPECT_EQ(260u, sps->width);
|
|
EXPECT_EQ(260u, sps->height);
|
|
}
|
|
|
|
TEST_F(H265SpsParserTest, TestSyntheticSPSQvgaLandscape) {
|
|
rtc::Buffer buffer;
|
|
WriteSps(320u, 180u, 1, 0, 1, 0, &buffer);
|
|
std::optional<H265SpsParser::SpsState> sps = H265SpsParser::ParseSps(buffer);
|
|
ASSERT_TRUE(sps.has_value());
|
|
EXPECT_EQ(320u, sps->width);
|
|
EXPECT_EQ(180u, sps->height);
|
|
EXPECT_EQ(1u, sps->sps_id);
|
|
}
|
|
|
|
TEST_F(H265SpsParserTest, TestSyntheticSPSWeirdResolution) {
|
|
rtc::Buffer buffer;
|
|
WriteSps(156u, 122u, 2, 0, 1, 0, &buffer);
|
|
std::optional<H265SpsParser::SpsState> sps = H265SpsParser::ParseSps(buffer);
|
|
ASSERT_TRUE(sps.has_value());
|
|
EXPECT_EQ(156u, sps->width);
|
|
EXPECT_EQ(122u, sps->height);
|
|
EXPECT_EQ(2u, sps->sps_id);
|
|
}
|
|
|
|
TEST_F(H265SpsParserTest, TestLog2MaxSubLayersMinus1) {
|
|
rtc::Buffer buffer;
|
|
WriteSps(320u, 180u, 1, 0, 1, 0, &buffer);
|
|
std::optional<H265SpsParser::SpsState> sps = H265SpsParser::ParseSps(buffer);
|
|
ASSERT_TRUE(sps.has_value());
|
|
EXPECT_EQ(320u, sps->width);
|
|
EXPECT_EQ(180u, sps->height);
|
|
EXPECT_EQ(1u, sps->sps_id);
|
|
EXPECT_EQ(0u, sps->sps_max_sub_layers_minus1);
|
|
|
|
WriteSps(320u, 180u, 1, 6, 1, 0, &buffer);
|
|
std::optional<H265SpsParser::SpsState> sps1 = H265SpsParser::ParseSps(buffer);
|
|
ASSERT_TRUE(sps1.has_value());
|
|
EXPECT_EQ(320u, sps1->width);
|
|
EXPECT_EQ(180u, sps1->height);
|
|
EXPECT_EQ(1u, sps1->sps_id);
|
|
EXPECT_EQ(6u, sps1->sps_max_sub_layers_minus1);
|
|
|
|
WriteSps(320u, 180u, 1, 7, 1, 0, &buffer);
|
|
std::optional<H265SpsParser::SpsState> result =
|
|
H265SpsParser::ParseSps(buffer);
|
|
EXPECT_FALSE(result.has_value());
|
|
}
|
|
|
|
TEST_F(H265SpsParserTest, TestSubLayerOrderingInfoPresentFlag) {
|
|
rtc::Buffer buffer;
|
|
WriteSps(320u, 180u, 1, 6, 1, 0, &buffer);
|
|
std::optional<H265SpsParser::SpsState> sps = H265SpsParser::ParseSps(buffer);
|
|
ASSERT_TRUE(sps.has_value());
|
|
EXPECT_EQ(320u, sps->width);
|
|
EXPECT_EQ(180u, sps->height);
|
|
EXPECT_EQ(1u, sps->sps_id);
|
|
EXPECT_EQ(6u, sps->sps_max_sub_layers_minus1);
|
|
|
|
WriteSps(320u, 180u, 1, 6, 1, 0, &buffer);
|
|
std::optional<H265SpsParser::SpsState> sps1 = H265SpsParser::ParseSps(buffer);
|
|
ASSERT_TRUE(sps1.has_value());
|
|
EXPECT_EQ(320u, sps1->width);
|
|
EXPECT_EQ(180u, sps1->height);
|
|
EXPECT_EQ(1u, sps1->sps_id);
|
|
EXPECT_EQ(6u, sps1->sps_max_sub_layers_minus1);
|
|
}
|
|
|
|
TEST_F(H265SpsParserTest, TestLongTermRefPicsPresentFlag) {
|
|
rtc::Buffer buffer;
|
|
WriteSps(320u, 180u, 1, 0, 1, 0, &buffer);
|
|
std::optional<H265SpsParser::SpsState> sps = H265SpsParser::ParseSps(buffer);
|
|
ASSERT_TRUE(sps.has_value());
|
|
EXPECT_EQ(320u, sps->width);
|
|
EXPECT_EQ(180u, sps->height);
|
|
EXPECT_EQ(1u, sps->sps_id);
|
|
EXPECT_EQ(0u, sps->long_term_ref_pics_present_flag);
|
|
|
|
WriteSps(320u, 180u, 1, 6, 1, 1, &buffer);
|
|
std::optional<H265SpsParser::SpsState> sps1 = H265SpsParser::ParseSps(buffer);
|
|
ASSERT_TRUE(sps1.has_value());
|
|
EXPECT_EQ(320u, sps1->width);
|
|
EXPECT_EQ(180u, sps1->height);
|
|
EXPECT_EQ(1u, sps1->sps_id);
|
|
EXPECT_EQ(1u, sps1->long_term_ref_pics_present_flag);
|
|
}
|
|
|
|
} // namespace webrtc
|