diff --git a/api/BUILD.gn b/api/BUILD.gn index 35c5926ab9..935cb12c39 100644 --- a/api/BUILD.gn +++ b/api/BUILD.gn @@ -1606,6 +1606,10 @@ if (rtc_include_tests) { "//third_party/abseil-cpp/absl/strings", "//third_party/abseil-cpp/absl/strings:string_view", ] + + if (rtc_use_h265) { + deps += [ "video:rtp_video_frame_h265_assembler_unittests" ] + } } rtc_library("compile_all_headers") { diff --git a/api/video/BUILD.gn b/api/video/BUILD.gn index 106140e22f..fd6cbd219c 100644 --- a/api/video/BUILD.gn +++ b/api/video/BUILD.gn @@ -233,6 +233,27 @@ rtc_library("rtp_video_frame_assembler_unittests") { ] } +if (rtc_use_h265) { + rtc_library("rtp_video_frame_h265_assembler_unittests") { + testonly = true + sources = [ "rtp_video_frame_h265_assembler_unittests.cc" ] + + deps = [ + ":encoded_frame", + ":rtp_video_frame_assembler", + ":video_frame", + ":video_frame_type", + "..:array_view", + "../../modules/rtp_rtcp:rtp_rtcp", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../modules/rtp_rtcp:rtp_video_header", + "../../modules/video_coding:codec_globals_headers", + "../../rtc_base:checks", + "../../test:test_support", + ] + } +} + rtc_source_set("video_codec_constants") { visibility = [ "*" ] sources = [ "video_codec_constants.h" ] diff --git a/api/video/rtp_video_frame_assembler.cc b/api/video/rtp_video_frame_assembler.cc index 2485eb34a8..ac71105e98 100644 --- a/api/video/rtp_video_frame_assembler.cc +++ b/api/video/rtp_video_frame_assembler.cc @@ -43,6 +43,10 @@ #include "rtc_base/logging.h" #include "rtc_base/numerics/sequence_number_unwrapper.h" +#ifdef RTC_ENABLE_H265 +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_h265.h" +#endif + namespace webrtc { namespace { std::unique_ptr CreateDepacketizer( @@ -61,9 +65,11 @@ std::unique_ptr CreateDepacketizer( case RtpVideoFrameAssembler::kGeneric: return std::make_unique(); case RtpVideoFrameAssembler::kH265: - // TODO(bugs.webrtc.org/13485): Implement VideoRtpDepacketizerH265 - RTC_DCHECK_NOTREACHED(); +#ifdef RTC_ENABLE_H265 + return std::make_unique(); +#else return nullptr; +#endif } RTC_DCHECK_NOTREACHED(); return nullptr; diff --git a/api/video/rtp_video_frame_h265_assembler_unittests.cc b/api/video/rtp_video_frame_h265_assembler_unittests.cc new file mode 100644 index 0000000000..3c9d02096b --- /dev/null +++ b/api/video/rtp_video_frame_h265_assembler_unittests.cc @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2024 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 +#include +#include +#include + +#include "api/array_view.h" +#include "api/video/encoded_frame.h" +#include "api/video/rtp_video_frame_assembler.h" +#include "api/video/video_codec_type.h" +#include "api/video/video_frame_type.h" +#include "modules/rtp_rtcp/source/rtp_format.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "rtc_base/checks.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAreArray; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::SizeIs; +using ::testing::UnorderedElementsAre; +using PayloadFormat = RtpVideoFrameAssembler::PayloadFormat; + +class PacketBuilder { + public: + explicit PacketBuilder(PayloadFormat format) + : format_(format), packet_to_send_(&extension_manager_) {} + + PacketBuilder& WithSeqNum(uint16_t seq_num) { + seq_num_ = seq_num; + return *this; + } + + PacketBuilder& WithPayload(rtc::ArrayView payload) { + payload_.assign(payload.begin(), payload.end()); + return *this; + } + + PacketBuilder& WithVideoHeader(const RTPVideoHeader& video_header) { + video_header_ = video_header; + return *this; + } + + template + PacketBuilder& WithExtension(int id, const Args&... args) { + extension_manager_.Register(id); + packet_to_send_.IdentifyExtensions(extension_manager_); + packet_to_send_.SetExtension(std::forward(args)...); + return *this; + } + + RtpPacketReceived Build() { + auto packetizer = + RtpPacketizer::Create(GetVideoCodecType(), payload_, {}, video_header_); + packetizer->NextPacket(&packet_to_send_); + packet_to_send_.SetSequenceNumber(seq_num_); + + RtpPacketReceived received(&extension_manager_); + received.Parse(packet_to_send_.Buffer()); + return received; + } + + private: + std::optional GetVideoCodecType() { + switch (format_) { + case PayloadFormat::kH265: { + return kVideoCodecH265; + } + default: + RTC_DCHECK_NOTREACHED(); + return std::nullopt; + } + } + + const RtpVideoFrameAssembler::PayloadFormat format_; + uint16_t seq_num_ = 0; + std::vector payload_; + RTPVideoHeader video_header_; + RtpPacketReceived::ExtensionManager extension_manager_; + RtpPacketToSend packet_to_send_; +}; + +void AppendFrames(RtpVideoFrameAssembler::FrameVector&& from, + RtpVideoFrameAssembler::FrameVector& to) { + to.insert(to.end(), std::make_move_iterator(from.begin()), + std::make_move_iterator(from.end())); +} + +rtc::ArrayView References(const std::unique_ptr& frame) { + return rtc::MakeArrayView(frame->references, frame->num_references); +} + +rtc::ArrayView Payload(const std::unique_ptr& frame) { + return rtc::ArrayView(*frame->GetEncodedData()); +} + +TEST(RtpVideoFrameH265Assembler, H265Packetization) { + RtpVideoFrameAssembler assembler(RtpVideoFrameAssembler::kH265); + RtpVideoFrameAssembler::FrameVector frames; + + // Key and delta frames generated on linux with ffmpeg command: + // `ffmpeg -i /dev/video0 -r 30 -c:v libx265 -s 1280x720 camera.h265`, + // truncated for test. + // IDR_N_LP(key) frame with start code included. + uint8_t kIdrPayload[] = {0x00, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, + 0x08, 0x4a, 0x31, 0x11, 0x15, 0xe5, 0xc0}; + // TRAIL_R(delta) frame with start code included. + uint8_t kDeltaPayload[] = {0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0xd0, + 0x09, 0x7e, 0x10, 0xc6, 0x1c, 0x8c, 0x17}; + + RTPVideoHeader video_header; + video_header.frame_type = VideoFrameType::kVideoFrameKey; + RtpVideoFrameAssembler::FrameVector idr_frames = + assembler.InsertPacket(PacketBuilder(PayloadFormat::kH265) + .WithPayload(kIdrPayload) + .WithVideoHeader(video_header) + .WithSeqNum(10) + .Build()); + AppendFrames(std::move(idr_frames), frames); + + RtpVideoFrameAssembler::FrameVector delta_frames = + assembler.InsertPacket(PacketBuilder(PayloadFormat::kH265) + .WithPayload(kDeltaPayload) + .WithSeqNum(11) + .Build()); + AppendFrames(std::move(delta_frames), frames); + ASSERT_THAT(frames, SizeIs(2)); + + auto first_frame = frames[0].ExtractFrame(); + EXPECT_THAT(first_frame->Id(), Eq(10)); + EXPECT_THAT(Payload(first_frame), ElementsAreArray(kIdrPayload)); + EXPECT_THAT(References(first_frame), IsEmpty()); + + auto second_frame = frames[1].ExtractFrame(); + EXPECT_THAT(second_frame->Id(), Eq(11)); + EXPECT_THAT(Payload(second_frame), ElementsAreArray(kDeltaPayload)); + EXPECT_THAT(References(second_frame), UnorderedElementsAre(10)); +} + +} // namespace +} // namespace webrtc