|  | /* | 
|  | *  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 |