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:
parent
07f90d914c
commit
abadbce1fa
@ -519,6 +519,20 @@ H265BitstreamParser::ParsePpsIdFromSliceSegmentLayerRbsp(
|
|||||||
return slice_pic_parameter_set_id;
|
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(
|
void H265BitstreamParser::ParseBitstream(
|
||||||
rtc::ArrayView<const uint8_t> bitstream) {
|
rtc::ArrayView<const uint8_t> bitstream) {
|
||||||
std::vector<H265::NaluIndex> nalu_indices = H265::FindNaluIndices(bitstream);
|
std::vector<H265::NaluIndex> nalu_indices = H265::FindNaluIndices(bitstream);
|
||||||
|
|||||||
@ -43,6 +43,11 @@ class RTC_EXPORT H265BitstreamParser : public BitstreamParser {
|
|||||||
rtc::ArrayView<const uint8_t> data,
|
rtc::ArrayView<const uint8_t> data,
|
||||||
uint8_t nalu_type);
|
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:
|
protected:
|
||||||
enum Result {
|
enum Result {
|
||||||
kOk,
|
kOk,
|
||||||
|
|||||||
@ -11,8 +11,12 @@
|
|||||||
#include "common_video/h265/h265_bitstream_parser.h"
|
#include "common_video/h265/h265_bitstream_parser.h"
|
||||||
|
|
||||||
#include "common_video/h265/h265_common.h"
|
#include "common_video/h265/h265_common.h"
|
||||||
|
#include "test/gmock.h"
|
||||||
#include "test/gtest.h"
|
#include "test/gtest.h"
|
||||||
|
|
||||||
|
using ::testing::Eq;
|
||||||
|
using ::testing::Optional;
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
// VPS/SPS/PPS part of below chunk.
|
// 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,
|
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.
|
// Contains short term ref pic set slice to verify Log2Ceiling path.
|
||||||
const uint8_t kH265SliceStrChunk[] = {
|
const uint8_t kH265SliceStrChunk[] = {
|
||||||
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x00,
|
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x00,
|
||||||
@ -144,4 +154,22 @@ TEST(H265BitstreamParserTest, ReportsLastSliceQpInvalidQPSlices) {
|
|||||||
ASSERT_FALSE(qp.has_value());
|
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
|
} // namespace webrtc
|
||||||
|
|||||||
@ -50,6 +50,7 @@ enum NaluType : uint8_t {
|
|||||||
kIdrNLp = 20,
|
kIdrNLp = 20,
|
||||||
kCra = 21,
|
kCra = 21,
|
||||||
kRsvIrapVcl23 = 23,
|
kRsvIrapVcl23 = 23,
|
||||||
|
kRsvVcl31 = 31,
|
||||||
kVps = 32,
|
kVps = 32,
|
||||||
kSps = 33,
|
kSps = 33,
|
||||||
kPps = 34,
|
kPps = 34,
|
||||||
|
|||||||
@ -63,10 +63,8 @@ bool ParseApStartOffsets(const uint8_t* nalu_ptr,
|
|||||||
// https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2
|
// https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2
|
||||||
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ProcessApOrSingleNalu(
|
std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ProcessApOrSingleNalu(
|
||||||
rtc::CopyOnWriteBuffer rtp_payload) {
|
rtc::CopyOnWriteBuffer rtp_payload) {
|
||||||
// Skip the single NALU header (payload header), aggregated packet case will
|
if (rtp_payload.size() < kH265PayloadHeaderSizeBytes) {
|
||||||
// be checked later.
|
RTC_LOG(LS_ERROR) << "RTP payload truncated.";
|
||||||
if (rtp_payload.size() <= kH265PayloadHeaderSizeBytes) {
|
|
||||||
RTC_LOG(LS_ERROR) << "Single NALU header truncated.";
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
const uint8_t* const payload_data = rtp_payload.cdata();
|
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.width = 0;
|
||||||
parsed_payload->video_header.height = 0;
|
parsed_payload->video_header.height = 0;
|
||||||
parsed_payload->video_header.codec = kVideoCodecH265;
|
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 uint8_t* nalu_start = payload_data + kH265PayloadHeaderSizeBytes;
|
||||||
const size_t nalu_length = rtp_payload.size() - 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::kIdrWRadl:
|
||||||
case H265::NaluType::kIdrNLp:
|
case H265::NaluType::kIdrNLp:
|
||||||
case H265::NaluType::kCra:
|
case H265::NaluType::kCra:
|
||||||
case H265::NaluType::kRsvIrapVcl23:
|
|
||||||
// Mark IRAP(Intra Random Access Point) frames as key frames. Their NALU
|
// 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),
|
// types are in the range of BLA_W_LP (16) to CRA (21), inclusive.
|
||||||
// inclusive.
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7798#section-3.1.1
|
// https://datatracker.ietf.org/doc/html/rfc7798#section-3.1.1
|
||||||
parsed_payload->video_header.frame_type =
|
parsed_payload->video_header.frame_type =
|
||||||
VideoFrameType::kVideoFrameKey;
|
VideoFrameType::kVideoFrameKey;
|
||||||
break;
|
break;
|
||||||
case H265::NaluType::kSps: {
|
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 =
|
std::optional<H265SpsParser::SpsState> sps =
|
||||||
H265SpsParser::ParseSps(nalu_data);
|
H265SpsParser::ParseSps(nalu_data);
|
||||||
|
|
||||||
@ -160,14 +151,14 @@ std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ProcessApOrSingleNalu(
|
|||||||
case H265::NaluType::kPps:
|
case H265::NaluType::kPps:
|
||||||
case H265::NaluType::kTrailN:
|
case H265::NaluType::kTrailN:
|
||||||
case H265::NaluType::kTrailR:
|
case H265::NaluType::kTrailR:
|
||||||
// Slices below don't contain SPS or PPS ids.
|
|
||||||
case H265::NaluType::kAud:
|
|
||||||
case H265::NaluType::kTsaN:
|
case H265::NaluType::kTsaN:
|
||||||
case H265::NaluType::kTsaR:
|
case H265::NaluType::kTsaR:
|
||||||
case H265::NaluType::kStsaN:
|
case H265::NaluType::kStsaN:
|
||||||
case H265::NaluType::kStsaR:
|
case H265::NaluType::kStsaR:
|
||||||
case H265::NaluType::kRadlN:
|
case H265::NaluType::kRadlN:
|
||||||
case H265::NaluType::kRadlR:
|
case H265::NaluType::kRadlR:
|
||||||
|
// Slices below don't contain SPS or PPS ids.
|
||||||
|
case H265::NaluType::kAud:
|
||||||
case H265::NaluType::kPrefixSei:
|
case H265::NaluType::kPrefixSei:
|
||||||
case H265::NaluType::kSuffixSei:
|
case H265::NaluType::kSuffixSei:
|
||||||
break;
|
break;
|
||||||
@ -177,6 +168,20 @@ std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ProcessApOrSingleNalu(
|
|||||||
RTC_LOG(LS_WARNING) << "Unexpected AP, FU or PACI received.";
|
RTC_LOG(LS_WARNING) << "Unexpected AP, FU or PACI received.";
|
||||||
return std::nullopt;
|
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;
|
parsed_payload->video_payload = video_payload;
|
||||||
return parsed_payload;
|
return parsed_payload;
|
||||||
@ -200,7 +205,20 @@ std::optional<VideoRtpDepacketizer::ParsedRtpPayload> ParseFuNalu(
|
|||||||
|
|
||||||
uint8_t original_nal_type = rtp_payload.cdata()[2] & kH265TypeMaskInFuHeader;
|
uint8_t original_nal_type = rtp_payload.cdata()[2] & kH265TypeMaskInFuHeader;
|
||||||
bool first_fragment = rtp_payload.cdata()[2] & kH265SBitMask;
|
bool first_fragment = rtp_payload.cdata()[2] & kH265SBitMask;
|
||||||
|
bool is_first_packet_in_frame = false;
|
||||||
if (first_fragment) {
|
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(
|
rtp_payload = rtp_payload.Slice(
|
||||||
kH265FuHeaderSizeBytes, rtp_payload.size() - kH265FuHeaderSizeBytes);
|
kH265FuHeaderSizeBytes, rtp_payload.size() - kH265FuHeaderSizeBytes);
|
||||||
rtp_payload.MutableData()[0] = f | original_nal_type << 1 | layer_id_h;
|
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.width = 0;
|
||||||
parsed_payload->video_header.height = 0;
|
parsed_payload->video_header.height = 0;
|
||||||
parsed_payload->video_header.codec = kVideoCodecH265;
|
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;
|
return parsed_payload;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -388,14 +388,255 @@ TEST(VideoRtpDepacketizerH265Test, InvalidNaluSizeApNalu) {
|
|||||||
EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload)));
|
EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(VideoRtpDepacketizerH265Test, SeiPacket) {
|
TEST(VideoRtpDepacketizerH265Test, PrefixSeiSetsFirstPacketInFrame) {
|
||||||
const uint8_t kPayload[] = {
|
const uint8_t kPayload[] = {
|
||||||
0x4e, 0x02, // F=0, Type=39 (kPrefixSei).
|
0x4e, 0x02, // F=0, Type=39 (H265::kPrefixSei).
|
||||||
0x03, 0x03, 0x03, 0x03 // Payload.
|
0x03, 0x03, 0x03, 0x03 // Payload.
|
||||||
};
|
};
|
||||||
VideoRtpDepacketizerH265 depacketizer;
|
VideoRtpDepacketizerH265 depacketizer;
|
||||||
auto parsed = depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload));
|
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
|
} // namespace
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user