Update is_first_packet_in_frame flag in H.265 depacketizer

Follow H.265 spec section 7.4.2.4.4 to set is_first_packet_in_frame flag
in rtp header, to make sure we only set it to true when corresponding
NALU can be the first NALU in a frame.

Bug: chromium:384391181
Change-Id: I082c38513d9d213f8d354633539028b57777368f
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/372742
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43651}
This commit is contained in:
Qiu Jianlin 2025-01-02 21:30:37 +08:00 committed by WebRTC LUCI CQ
parent 07f90d914c
commit abadbce1fa
6 changed files with 327 additions and 19 deletions

View File

@ -519,6 +519,20 @@ H265BitstreamParser::ParsePpsIdFromSliceSegmentLayerRbsp(
return slice_pic_parameter_set_id;
}
std::optional<bool> H265BitstreamParser::IsFirstSliceSegmentInPic(
rtc::ArrayView<const uint8_t> data) {
std::vector<uint8_t> unpacked_buffer = H265::ParseRbsp(data);
BitstreamReader slice_reader(unpacked_buffer);
// first_slice_segment_in_pic_flag: u(1)
bool first_slice_segment_in_pic_flag = slice_reader.Read<bool>();
if (!slice_reader.Ok()) {
return std::nullopt;
}
return first_slice_segment_in_pic_flag;
}
void H265BitstreamParser::ParseBitstream(
rtc::ArrayView<const uint8_t> bitstream) {
std::vector<H265::NaluIndex> nalu_indices = H265::FindNaluIndices(bitstream);

View File

@ -43,6 +43,11 @@ class RTC_EXPORT H265BitstreamParser : public BitstreamParser {
rtc::ArrayView<const uint8_t> data,
uint8_t nalu_type);
// Returns true if the slice segment is the first in the picture; otherwise
// return false. If parse failed, return nullopt.
static std::optional<bool> IsFirstSliceSegmentInPic(
rtc::ArrayView<const uint8_t> data);
protected:
enum Result {
kOk,

View File

@ -11,8 +11,12 @@
#include "common_video/h265/h265_bitstream_parser.h"
#include "common_video/h265/h265_common.h"
#include "test/gmock.h"
#include "test/gtest.h"
using ::testing::Eq;
using ::testing::Optional;
namespace webrtc {
// VPS/SPS/PPS part of below chunk.
@ -53,6 +57,12 @@ const uint8_t kH265SliceChunk[] = {
0x26, 0x0f, 0x7b, 0x30, 0x1c, 0xd7, 0xd4, 0x3a, 0xec, 0xad, 0xef, 0x73,
};
// Contains enough of data for the second slice of a frame.
const uint8_t kH265SecondSliceChunkInAFrame[] = {
0x02, 0x01, 0x23, 0xfc, 0x20, 0x22, 0xad, 0x13, 0x68, 0xce, 0xc3, 0x5a,
0x00, 0xdc, 0xeb, 0x86, 0x4b, 0x0b, 0xa7, 0x6a, 0xe1, 0x9c, 0x5c, 0xea,
};
// Contains short term ref pic set slice to verify Log2Ceiling path.
const uint8_t kH265SliceStrChunk[] = {
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x00,
@ -144,4 +154,22 @@ TEST(H265BitstreamParserTest, ReportsLastSliceQpInvalidQPSlices) {
ASSERT_FALSE(qp.has_value());
}
TEST(H265BitstreamParserTest, ReportsFirstSliceSegmentInPic) {
EXPECT_THAT(H265BitstreamParser::IsFirstSliceSegmentInPic(kH265SliceChunk),
Optional(Eq(true)));
}
TEST(H265BitstreamParserTest, ReportsFirstSliceSegmentInPicFalse) {
EXPECT_THAT(H265BitstreamParser::IsFirstSliceSegmentInPic(
kH265SecondSliceChunkInAFrame),
Optional(Eq(false)));
}
TEST(H265BitstreamParserTest, ReportsFirstSliceSegmentInPicParseInvalidSlice) {
rtc::ArrayView<const uint8_t> slice_data(kH265SliceChunk);
EXPECT_THAT(
H265BitstreamParser::IsFirstSliceSegmentInPic(slice_data.subview(50)),
Eq(std::nullopt));
}
} // namespace webrtc

View File

@ -50,6 +50,7 @@ enum NaluType : uint8_t {
kIdrNLp = 20,
kCra = 21,
kRsvIrapVcl23 = 23,
kRsvVcl31 = 31,
kVps = 32,
kSps = 33,
kPps = 34,

View File

@ -63,10 +63,8 @@ bool ParseApStartOffsets(const uint8_t* nalu_ptr,
// https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ProcessApOrSingleNalu(
rtc::CopyOnWriteBuffer rtp_payload) {
// Skip the single NALU header (payload header), aggregated packet case will
// be checked later.
if (rtp_payload.size() <= kH265PayloadHeaderSizeBytes) {
RTC_LOG(LS_ERROR) << "Single NALU header truncated.";
if (rtp_payload.size() < kH265PayloadHeaderSizeBytes) {
RTC_LOG(LS_ERROR) << "RTP payload truncated.";
return std::nullopt;
}
const uint8_t* const payload_data = rtp_payload.cdata();
@ -75,7 +73,7 @@ std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ProcessApOrSingleNalu(
parsed_payload->video_header.width = 0;
parsed_payload->video_header.height = 0;
parsed_payload->video_header.codec = kVideoCodecH265;
parsed_payload->video_header.is_first_packet_in_frame = true;
parsed_payload->video_header.is_first_packet_in_frame = false;
const uint8_t* nalu_start = payload_data + kH265PayloadHeaderSizeBytes;
const size_t nalu_length = rtp_payload.size() - kH265PayloadHeaderSizeBytes;
@ -129,20 +127,13 @@ std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ProcessApOrSingleNalu(
case H265::NaluType::kIdrWRadl:
case H265::NaluType::kIdrNLp:
case H265::NaluType::kCra:
case H265::NaluType::kRsvIrapVcl23:
// Mark IRAP(Intra Random Access Point) frames as key frames. Their NALU
// types are in the range of BLA_W_LP (16) to RSV_IRAP_VCL23 (23),
// inclusive.
// types are in the range of BLA_W_LP (16) to CRA (21), inclusive.
// https://datatracker.ietf.org/doc/html/rfc7798#section-3.1.1
parsed_payload->video_header.frame_type =
VideoFrameType::kVideoFrameKey;
break;
case H265::NaluType::kSps: {
// Copy any previous data first (likely just the first header).
std::unique_ptr<rtc::Buffer> output_buffer(new rtc::Buffer());
if (start_offset)
output_buffer->AppendData(payload_data, start_offset);
std::optional<H265SpsParser::SpsState> sps =
H265SpsParser::ParseSps(nalu_data);
@ -160,14 +151,14 @@ std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ProcessApOrSingleNalu(
case H265::NaluType::kPps:
case H265::NaluType::kTrailN:
case H265::NaluType::kTrailR:
// Slices below don't contain SPS or PPS ids.
case H265::NaluType::kAud:
case H265::NaluType::kTsaN:
case H265::NaluType::kTsaR:
case H265::NaluType::kStsaN:
case H265::NaluType::kStsaR:
case H265::NaluType::kRadlN:
case H265::NaluType::kRadlR:
// Slices below don't contain SPS or PPS ids.
case H265::NaluType::kAud:
case H265::NaluType::kPrefixSei:
case H265::NaluType::kSuffixSei:
break;
@ -177,6 +168,20 @@ std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ProcessApOrSingleNalu(
RTC_LOG(LS_WARNING) << "Unexpected AP, FU or PACI received.";
return std::nullopt;
}
// Spec 7.4.2.4.4: Order of NAL units and codec pictures.
if ((nalu_type >= H265::NaluType::kVps &&
nalu_type <= H265::NaluType::kAud) ||
nalu_type == H265::NaluType::kPrefixSei) {
parsed_payload->video_header.is_first_packet_in_frame = true;
} else if (nalu_type >= H265::NaluType::kTrailN &&
nalu_type <= H265::NaluType::kRsvVcl31) {
std::optional<bool> first_slice_segment_in_pic_flag =
H265BitstreamParser::IsFirstSliceSegmentInPic(nalu_data);
if (first_slice_segment_in_pic_flag.value_or(false)) {
parsed_payload->video_header.is_first_packet_in_frame = true;
}
}
}
parsed_payload->video_payload = video_payload;
return parsed_payload;
@ -200,7 +205,20 @@ std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ParseFuNalu(
uint8_t original_nal_type = rtp_payload.cdata()[2] & kH265TypeMaskInFuHeader;
bool first_fragment = rtp_payload.cdata()[2] & kH265SBitMask;
bool is_first_packet_in_frame = false;
if (first_fragment) {
if (original_nal_type >= H265::NaluType::kTrailN &&
original_nal_type <= H265::NaluType::kRsvVcl31) {
size_t slice_offset =
kH265FuHeaderSizeBytes + kH265PayloadHeaderSizeBytes;
std::optional<bool> first_slice_segment_in_pic_flag =
H265BitstreamParser::IsFirstSliceSegmentInPic(
rtc::ArrayView<const uint8_t>(rtp_payload.cdata() + slice_offset,
rtp_payload.size() - slice_offset));
if (first_slice_segment_in_pic_flag.value_or(false)) {
is_first_packet_in_frame = true;
}
}
rtp_payload = rtp_payload.Slice(
kH265FuHeaderSizeBytes, rtp_payload.size() - kH265FuHeaderSizeBytes);
rtp_payload.MutableData()[0] = f | original_nal_type << 1 | layer_id_h;
@ -227,7 +245,8 @@ std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ParseFuNalu(
parsed_payload->video_header.width = 0;
parsed_payload->video_header.height = 0;
parsed_payload->video_header.codec = kVideoCodecH265;
parsed_payload->video_header.is_first_packet_in_frame = first_fragment;
parsed_payload->video_header.is_first_packet_in_frame =
is_first_packet_in_frame;
return parsed_payload;
}

View File

@ -388,14 +388,255 @@ TEST(VideoRtpDepacketizerH265Test, InvalidNaluSizeApNalu) {
EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload)));
}
TEST(VideoRtpDepacketizerH265Test, SeiPacket) {
TEST(VideoRtpDepacketizerH265Test, PrefixSeiSetsFirstPacketInFrame) {
const uint8_t kPayload[] = {
0x4e, 0x02, // F=0, Type=39 (kPrefixSei).
0x4e, 0x02, // F=0, Type=39 (H265::kPrefixSei).
0x03, 0x03, 0x03, 0x03 // Payload.
};
VideoRtpDepacketizerH265 depacketizer;
auto parsed = depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload));
ASSERT_TRUE(parsed);
ASSERT_TRUE(parsed.has_value());
EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame);
}
TEST(VideoRtpDepacketizerH265Test, ApVpsSpsPpsMultiIdrSlices) {
uint8_t payload_header[] = {0x60, 0x02};
uint8_t vps_nalu_size[] = {0, 0x17};
uint8_t sps_nalu_size[] = {0, 0x27};
uint8_t pps_nalu_size[] = {0, 0x32};
uint8_t slice_nalu_size[] = {0, 0xa};
uint8_t start_code[] = {0x00, 0x00, 0x00, 0x01};
// The VPS/SPS/PPS/IDR bytes are generated using the same way as above case.
// Slices are truncated to contain enough data for test.
uint8_t vps[] = {0x40, 0x02, 0x1c, 0x01, 0xff, 0xff, 0x04, 0x08,
0x00, 0x00, 0x03, 0x00, 0x9d, 0x08, 0x00, 0x00,
0x03, 0x00, 0x00, 0x78, 0x95, 0x98, 0x09};
uint8_t sps[] = {0x42, 0x02, 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};
uint8_t pps[] = {0x44, 0x02, 0xa4, 0x04, 0x55, 0xa2, 0x6d, 0xce, 0xc0, 0xc3,
0xed, 0x0b, 0xac, 0xbc, 0x00, 0xc4, 0x44, 0x2e, 0xf7, 0x55,
0xfd, 0x05, 0x86, 0x92, 0x19, 0xdf, 0x58, 0xec, 0x38, 0x36,
0xb7, 0x7c, 0x00, 0x15, 0x33, 0x78, 0x03, 0x67, 0x26, 0x0f,
0x7b, 0x30, 0x1c, 0xd7, 0xd4, 0x3a, 0xec, 0xad, 0xef, 0x73};
uint8_t idr_slice1[] = {0x28, 0x01, 0xac, 0x6d, 0xa0,
0x7b, 0x4c, 0xe2, 0x09, 0xef};
uint8_t idr_slice2[] = {0x28, 0x01, 0x27, 0xf8, 0x63,
0x6d, 0x7b, 0x6f, 0xcf, 0xff};
rtc::CopyOnWriteBuffer rtp_payload;
rtp_payload.AppendData(payload_header);
rtp_payload.AppendData(vps_nalu_size);
rtp_payload.AppendData(vps);
rtp_payload.AppendData(sps_nalu_size);
rtp_payload.AppendData(sps);
rtp_payload.AppendData(pps_nalu_size);
rtp_payload.AppendData(pps);
rtp_payload.AppendData(slice_nalu_size);
rtp_payload.AppendData(idr_slice1);
rtp_payload.AppendData(slice_nalu_size);
rtp_payload.AppendData(idr_slice2);
rtc::Buffer expected_packet;
expected_packet.AppendData(start_code);
expected_packet.AppendData(vps);
expected_packet.AppendData(start_code);
expected_packet.AppendData(sps);
expected_packet.AppendData(start_code);
expected_packet.AppendData(pps);
expected_packet.AppendData(start_code);
expected_packet.AppendData(idr_slice1);
expected_packet.AppendData(start_code);
expected_packet.AppendData(idr_slice2);
VideoRtpDepacketizerH265 depacketizer;
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed =
depacketizer.Parse(rtp_payload);
ASSERT_TRUE(parsed.has_value());
EXPECT_THAT(rtc::MakeArrayView(parsed->video_payload.cdata(),
parsed->video_payload.size()),
ElementsAreArray(expected_packet));
EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameKey);
EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame);
}
TEST(VideoRtpDepacketizerH265Test, ApMultiNonFirstSlicesFromSingleNonIdrFrame) {
uint8_t payload_header[] = {0x60, 0x02};
uint8_t slice_nalu_size[] = {0, 0xa};
uint8_t start_code[] = {0x00, 0x00, 0x00, 0x01};
// First few bytes of two non-IDR slices from the same frame, both with the
// first_slice_segment_in_pic_flag set to 0.
uint8_t non_idr_slice1[] = {0x02, 0x01, 0x23, 0xfc, 0x20,
0x42, 0xad, 0x1b, 0x68, 0xdf};
uint8_t non_idr_slice2[] = {0x02, 0x01, 0x27, 0xf8, 0x20,
0x42, 0xad, 0x1b, 0x68, 0xe0};
rtc::CopyOnWriteBuffer rtp_payload;
rtp_payload.AppendData(payload_header);
rtp_payload.AppendData(slice_nalu_size);
rtp_payload.AppendData(non_idr_slice1);
rtp_payload.AppendData(slice_nalu_size);
rtp_payload.AppendData(non_idr_slice2);
rtc::Buffer expected_packet;
expected_packet.AppendData(start_code);
expected_packet.AppendData(non_idr_slice1);
expected_packet.AppendData(start_code);
expected_packet.AppendData(non_idr_slice2);
VideoRtpDepacketizerH265 depacketizer;
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed =
depacketizer.Parse(rtp_payload);
ASSERT_TRUE(parsed.has_value());
EXPECT_THAT(rtc::MakeArrayView(parsed->video_payload.cdata(),
parsed->video_payload.size()),
ElementsAreArray(expected_packet));
EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameDelta);
EXPECT_FALSE(parsed->video_header.is_first_packet_in_frame);
}
TEST(VideoRtpDepacketizerH265Test, ApFirstTwoSlicesFromSingleNonIdrFrame) {
uint8_t payload_header[] = {0x60, 0x02};
uint8_t slice_nalu_size[] = {0, 0xa};
uint8_t start_code[] = {0x00, 0x00, 0x00, 0x01};
// First few bytes of two non-IDR slices from the same frame, with the first
// slice's first_slice_segment_in_pic_flag set to 1, and second set to 0.
uint8_t non_idr_slice1[] = {0x02, 0x01, 0xa4, 0x08, 0x55,
0xa3, 0x6d, 0xcc, 0xcf, 0x26};
uint8_t non_idr_slice2[] = {0x02, 0x01, 0x23, 0xfc, 0x20,
0x42, 0xad, 0x1b, 0x68, 0xdf};
rtc::CopyOnWriteBuffer rtp_payload;
rtp_payload.AppendData(payload_header);
rtp_payload.AppendData(slice_nalu_size);
rtp_payload.AppendData(non_idr_slice1);
rtp_payload.AppendData(slice_nalu_size);
rtp_payload.AppendData(non_idr_slice2);
rtc::Buffer expected_packet;
expected_packet.AppendData(start_code);
expected_packet.AppendData(non_idr_slice1);
expected_packet.AppendData(start_code);
expected_packet.AppendData(non_idr_slice2);
VideoRtpDepacketizerH265 depacketizer;
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed =
depacketizer.Parse(rtp_payload);
ASSERT_TRUE(parsed.has_value());
EXPECT_THAT(rtc::MakeArrayView(parsed->video_payload.cdata(),
parsed->video_payload.size()),
ElementsAreArray(expected_packet));
EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameDelta);
EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame);
}
TEST(VideoRtpDepacketizerH265Test, SingleNaluFromIdrSecondSlice) {
// First few bytes of the second slice of an IDR_N_LP nalu with
// first_slice_segment_in_pic_flag set to 0.
const uint8_t kPayload[] = {0x28, 0x01, 0x27, 0xf8, 0x63, 0x6d, 0x7b, 0x6f,
0xcf, 0xff, 0x0d, 0xf5, 0xc7, 0xfe, 0x57, 0x77,
0xdc, 0x29, 0x24, 0x89, 0x89, 0xea, 0xd1, 0x88};
VideoRtpDepacketizerH265 depacketizer;
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed =
depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload));
ASSERT_TRUE(parsed.has_value());
EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameKey);
EXPECT_FALSE(parsed->video_header.is_first_packet_in_frame);
}
TEST(VideoRtpDepacketizerH265Test, SingleNaluFromNonIdrSecondSlice) {
// First few bytes of the second slice of an TRAIL_R nalu with
// first_slice_segment_in_pic_flag set to 0.
const uint8_t kPayload[] = {0x02, 0x01, 0x23, 0xfc, 0x20, 0x22, 0xad, 0x13,
0x68, 0xce, 0xc3, 0x5a, 0x00, 0xdc, 0xeb, 0x86,
0x4b, 0x0b, 0xa7, 0x6a, 0xe1, 0x9c, 0x5c, 0xea};
VideoRtpDepacketizerH265 depacketizer;
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed =
depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload));
ASSERT_TRUE(parsed.has_value());
EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameDelta);
EXPECT_FALSE(parsed->video_header.is_first_packet_in_frame);
}
TEST(VideoRtpDepacketizerH265Test, FuFromIdrFrameSecondSlice) {
// First few bytes of the second slice of an IDR_N_LP nalu with
// first_slice_segment_in_pic_flag set to 0.
const uint8_t kPayload[] = {
0x62, 0x02, // F=0, Type=49 (H265::kFu).
0x93, // FU header kH265SBitMask | H265::kIdrWRadl.
0x23, 0xfc, 0x20, 0x22, 0xad, 0x13, 0x68, 0xce, 0xc3, 0x5a, 0x00, 0xdc};
VideoRtpDepacketizerH265 depacketizer;
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed =
depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload));
ASSERT_TRUE(parsed.has_value());
EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameKey);
EXPECT_FALSE(parsed->video_header.is_first_packet_in_frame);
}
TEST(VideoRtpDepacketizerH265Test, FuFromNonIdrFrameSecondSlice) {
// First few bytes of the second slice of an TRAIL_R nalu with
// first_slice_segment_in_pic_flag set to 0.
const uint8_t kPayload[] = {0x62, 0x02, // F=0, Type=49 (H265::kFu).
0x80, // FU header kH265SBitMask | H265::kTrailR.
0x23, 0xfc, 0x20, 0x22, 0xad, 0x13,
0x68, 0xce, 0xc3, 0x5a, 0x00, 0xdc};
VideoRtpDepacketizerH265 depacketizer;
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed =
depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload));
ASSERT_TRUE(parsed.has_value());
EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameDelta);
EXPECT_FALSE(parsed->video_header.is_first_packet_in_frame);
}
TEST(VideoRtpDepacketizerH265Test, AudSetsFirstPacketInFrame) {
const uint8_t kPayload[] = {0x46, 0x01, 0x10};
VideoRtpDepacketizerH265 depacketizer;
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed =
depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload));
ASSERT_TRUE(parsed.has_value());
EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame);
}
TEST(VideoRtpDepacketizerH265Test, PpsSetsFirstPacketInFrame) {
const uint8_t kPayload[] = {
0x44, 0x02, 0xa4, 0x04, 0x55, 0xa2, 0x6d, 0xce, 0xc0, 0xc3,
0xed, 0x0b, 0xac, 0xbc, 0x00, 0xc4, 0x44, 0x2e, 0xf7, 0x55,
0xfd, 0x05, 0x86, 0x92, 0x19, 0xdf, 0x58, 0xec, 0x38, 0x36,
0xb7, 0x7c, 0x00, 0x15, 0x33, 0x78, 0x03, 0x67, 0x26, 0x0f,
0x7b, 0x30, 0x1c, 0xd7, 0xd4, 0x3a, 0xec, 0xad, 0xef, 0x73};
VideoRtpDepacketizerH265 depacketizer;
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed =
depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload));
ASSERT_TRUE(parsed.has_value());
EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame);
}
TEST(VideoRtpDepacketizerH265Test, SuffixSeiNotSetFirstPacketInFrame) {
const uint8_t kPayload[] = {0x50, 0x01, 0x81, 0x01, 0x03, 0x80};
VideoRtpDepacketizerH265 depacketizer;
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed =
depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload));
ASSERT_TRUE(parsed.has_value());
EXPECT_FALSE(parsed->video_header.is_first_packet_in_frame);
}
TEST(VideoRtpDepacketizerH265Test, EmptyNaluPayload) {
const uint8_t kPayload[] = {0x48, 0x00}; // F=0, Type=36 (H265::kEos).
VideoRtpDepacketizerH265 depacketizer;
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed =
depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload));
ASSERT_TRUE(parsed.has_value());
}
} // namespace