From 61d6471912ec9a0cbef39c3b547e1701820bb46f Mon Sep 17 00:00:00 2001 From: Danil Chapovalov Date: Wed, 15 Jan 2020 11:34:28 +0100 Subject: [PATCH] Change H264 depacketizer to implement VideoRtpDepacketizer interface Bug: webrtc:11152 Change-Id: If5169f47d85918356fa66e2bf3422d722044aa1f Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/165581 Commit-Queue: Danil Chapovalov Reviewed-by: Markus Handell Reviewed-by: Sam Zackrisson Cr-Commit-Position: refs/heads/master@{#30264} --- .../source/create_video_rtp_depacketizer.cc | 32 +- modules/rtp_rtcp/source/rtp_format_h264.h | 1 - .../source/video_rtp_depacketizer_h264.cc | 180 ++++----- .../source/video_rtp_depacketizer_h264.h | 30 +- .../video_rtp_depacketizer_h264_unittest.cc | 378 +++++++++--------- test/fuzzers/h264_depacketizer_fuzzer.cc | 7 +- 6 files changed, 286 insertions(+), 342 deletions(-) diff --git a/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc b/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc index d96741b915..724ad8c42e 100644 --- a/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc +++ b/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc @@ -12,49 +12,21 @@ #include -#include "absl/memory/memory.h" -#include "absl/types/optional.h" +#include "api/video/video_codec_type.h" #include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" #include "modules/rtp_rtcp/source/video_rtp_depacketizer_av1.h" #include "modules/rtp_rtcp/source/video_rtp_depacketizer_generic.h" #include "modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h" #include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h" #include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h" -#include "rtc_base/checks.h" -#include "rtc_base/copy_on_write_buffer.h" namespace webrtc { -namespace { - -// Wrapper over legacy RtpDepacketizer interface. -// TODO(bugs.webrtc.org/11152): Delete when all RtpDepacketizers updated to -// the VideoRtpDepacketizer interface. -template -class Legacy : public VideoRtpDepacketizer { - public: - absl::optional Parse( - rtc::CopyOnWriteBuffer rtp_payload) override { - Depacketizer depacketizer; - RtpDepacketizer::ParsedPayload parsed_payload; - if (!depacketizer.Parse(&parsed_payload, rtp_payload.cdata(), - rtp_payload.size())) { - return absl::nullopt; - } - absl::optional result(absl::in_place); - result->video_header = parsed_payload.video; - result->video_payload.SetData(parsed_payload.payload, - parsed_payload.payload_length); - return result; - } -}; - -} // namespace std::unique_ptr CreateVideoRtpDepacketizer( VideoCodecType codec) { switch (codec) { case kVideoCodecH264: - return std::make_unique>(); + return std::make_unique(); case kVideoCodecVP8: return std::make_unique(); case kVideoCodecVP9: diff --git a/modules/rtp_rtcp/source/rtp_format_h264.h b/modules/rtp_rtcp/source/rtp_format_h264.h index fdea9a7607..4661dc2163 100644 --- a/modules/rtp_rtcp/source/rtp_format_h264.h +++ b/modules/rtp_rtcp/source/rtp_format_h264.h @@ -22,7 +22,6 @@ #include "modules/include/module_common_types.h" #include "modules/rtp_rtcp/source/rtp_format.h" #include "modules/rtp_rtcp/source/rtp_packet_to_send.h" -#include "modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h" #include "modules/video_coding/codecs/h264/include/h264_globals.h" #include "rtc_base/buffer.h" #include "rtc_base/constructor_magic.h" diff --git a/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc b/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc index 9babc67548..a0bd8fbc64 100644 --- a/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc +++ b/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc @@ -10,11 +10,8 @@ #include "modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h" -#include - #include #include -#include #include #include @@ -25,17 +22,19 @@ #include "common_video/h264/sps_parser.h" #include "common_video/h264/sps_vui_rewriter.h" #include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" #include "rtc_base/checks.h" +#include "rtc_base/copy_on_write_buffer.h" #include "rtc_base/logging.h" #include "rtc_base/system/fallthrough.h" namespace webrtc { namespace { -static const size_t kNalHeaderSize = 1; -static const size_t kFuAHeaderSize = 2; -static const size_t kLengthFieldSize = 2; -static const size_t kStapAHeaderSize = kNalHeaderSize + kLengthFieldSize; +constexpr size_t kNalHeaderSize = 1; +constexpr size_t kFuAHeaderSize = 2; +constexpr size_t kLengthFieldSize = 2; +constexpr size_t kStapAHeaderSize = kNalHeaderSize + kLengthFieldSize; // Bit masks for FU (A and B) indicators. enum NalDefs : uint8_t { kFBit = 0x80, kNriMask = 0x60, kTypeMask = 0x1F }; @@ -66,36 +65,35 @@ bool ParseStapAStartOffsets(const uint8_t* nalu_ptr, return true; } -} // namespace - -RtpDepacketizerH264::RtpDepacketizerH264() : offset_(0), length_(0) {} -RtpDepacketizerH264::~RtpDepacketizerH264() {} - -bool RtpDepacketizerH264::ProcessStapAOrSingleNalu( - ParsedPayload* parsed_payload, - const uint8_t* payload_data) { - parsed_payload->video_header().width = 0; - parsed_payload->video_header().height = 0; - parsed_payload->video_header().codec = kVideoCodecH264; - parsed_payload->video_header().simulcastIdx = 0; - parsed_payload->video_header().is_first_packet_in_frame = true; - auto& h264_header = absl::get( - parsed_payload->video_header().video_type_header); +absl::optional ProcessStapAOrSingleNalu( + rtc::CopyOnWriteBuffer rtp_payload) { + const uint8_t* const payload_data = rtp_payload.cdata(); + absl::optional parsed_payload( + absl::in_place); + bool modified_buffer = false; + parsed_payload->video_payload = rtp_payload; + parsed_payload->video_header.width = 0; + parsed_payload->video_header.height = 0; + parsed_payload->video_header.codec = kVideoCodecH264; + parsed_payload->video_header.simulcastIdx = 0; + parsed_payload->video_header.is_first_packet_in_frame = true; + auto& h264_header = parsed_payload->video_header.video_type_header + .emplace(); const uint8_t* nalu_start = payload_data + kNalHeaderSize; - const size_t nalu_length = length_ - kNalHeaderSize; + const size_t nalu_length = rtp_payload.size() - kNalHeaderSize; uint8_t nal_type = payload_data[0] & kTypeMask; std::vector nalu_start_offsets; if (nal_type == H264::NaluType::kStapA) { // Skip the StapA header (StapA NAL type + length). - if (length_ <= kStapAHeaderSize) { + if (rtp_payload.size() <= kStapAHeaderSize) { RTC_LOG(LS_ERROR) << "StapA header truncated."; - return false; + return absl::nullopt; } if (!ParseStapAStartOffsets(nalu_start, nalu_length, &nalu_start_offsets)) { RTC_LOG(LS_ERROR) << "StapA packet with incorrect NALU packet lengths."; - return false; + return absl::nullopt; } h264_header.packetization_type = kH264StapA; @@ -105,9 +103,10 @@ bool RtpDepacketizerH264::ProcessStapAOrSingleNalu( nalu_start_offsets.push_back(0); } h264_header.nalu_type = nal_type; - parsed_payload->video_header().frame_type = VideoFrameType::kVideoFrameDelta; + parsed_payload->video_header.frame_type = VideoFrameType::kVideoFrameDelta; - nalu_start_offsets.push_back(length_ + kLengthFieldSize); // End offset. + nalu_start_offsets.push_back(rtp_payload.size() + + kLengthFieldSize); // End offset. for (size_t i = 0; i < nalu_start_offsets.size() - 1; ++i) { size_t start_offset = nalu_start_offsets[i]; // End offset is actually start offset for next unit, excluding length field @@ -115,7 +114,7 @@ bool RtpDepacketizerH264::ProcessStapAOrSingleNalu( size_t end_offset = nalu_start_offsets[i + 1] - kLengthFieldSize; if (end_offset - start_offset < H264::kNaluTypeSize) { RTC_LOG(LS_ERROR) << "STAP-A packet too short"; - return false; + return absl::nullopt; } NaluInfo nalu; @@ -131,18 +130,18 @@ bool RtpDepacketizerH264::ProcessStapAOrSingleNalu( // excessive decoder latency. // Copy any previous data first (likely just the first header). - std::unique_ptr output_buffer(new rtc::Buffer()); + rtc::Buffer output_buffer; if (start_offset) - output_buffer->AppendData(payload_data, start_offset); + output_buffer.AppendData(payload_data, start_offset); absl::optional sps; SpsVuiRewriter::ParseResult result = SpsVuiRewriter::ParseAndRewriteSps( &payload_data[start_offset], end_offset - start_offset, &sps, - nullptr, output_buffer.get(), SpsVuiRewriter::Direction::kIncoming); + nullptr, &output_buffer, SpsVuiRewriter::Direction::kIncoming); if (result == SpsVuiRewriter::ParseResult::kVuiRewritten) { - if (modified_buffer_) { + if (modified_buffer) { RTC_LOG(LS_WARNING) << "More than one H264 SPS NAL units needing " "rewriting found within a single STAP-A packet. " @@ -155,27 +154,29 @@ bool RtpDepacketizerH264::ProcessStapAOrSingleNalu( start_offset - (H264::kNaluTypeSize + kLengthFieldSize); // Stap-A Length includes payload data and type header. size_t rewritten_size = - output_buffer->size() - start_offset + H264::kNaluTypeSize; + output_buffer.size() - start_offset + H264::kNaluTypeSize; ByteWriter::WriteBigEndian( - &(*output_buffer)[length_field_offset], rewritten_size); + &output_buffer[length_field_offset], rewritten_size); } + parsed_payload->video_payload.SetData(output_buffer.data(), + output_buffer.size()); // Append rest of packet. - output_buffer->AppendData(&payload_data[end_offset], - nalu_length + kNalHeaderSize - end_offset); + parsed_payload->video_payload.AppendData( + &payload_data[end_offset], + nalu_length + kNalHeaderSize - end_offset); - modified_buffer_ = std::move(output_buffer); - length_ = modified_buffer_->size(); + modified_buffer = true; } if (sps) { - parsed_payload->video_header().width = sps->width; - parsed_payload->video_header().height = sps->height; + parsed_payload->video_header.width = sps->width; + parsed_payload->video_header.height = sps->height; nalu.sps_id = sps->id; } else { RTC_LOG(LS_WARNING) << "Failed to parse SPS id from SPS slice."; } - parsed_payload->video_header().frame_type = + parsed_payload->video_header.frame_type = VideoFrameType::kVideoFrameKey; break; } @@ -194,7 +195,7 @@ bool RtpDepacketizerH264::ProcessStapAOrSingleNalu( break; } case H264::NaluType::kIdr: - parsed_payload->video_header().frame_type = + parsed_payload->video_header.frame_type = VideoFrameType::kVideoFrameKey; RTC_FALLTHROUGH(); case H264::NaluType::kSlice: { @@ -218,7 +219,7 @@ bool RtpDepacketizerH264::ProcessStapAOrSingleNalu( case H264::NaluType::kStapA: case H264::NaluType::kFuA: RTC_LOG(LS_WARNING) << "Unexpected STAP-A or FU-A received."; - return false; + return absl::nullopt; } if (h264_header.nalus_length == kMaxNalusPerPacket) { @@ -230,28 +231,28 @@ bool RtpDepacketizerH264::ProcessStapAOrSingleNalu( } } - return true; + return parsed_payload; } -bool RtpDepacketizerH264::ParseFuaNalu( - RtpDepacketizer::ParsedPayload* parsed_payload, - const uint8_t* payload_data) { - if (length_ < kFuAHeaderSize) { +absl::optional ParseFuaNalu( + rtc::CopyOnWriteBuffer rtp_payload) { + if (rtp_payload.size() < kFuAHeaderSize) { RTC_LOG(LS_ERROR) << "FU-A NAL units truncated."; - return false; + return absl::nullopt; } - uint8_t fnri = payload_data[0] & (kFBit | kNriMask); - uint8_t original_nal_type = payload_data[1] & kTypeMask; - bool first_fragment = (payload_data[1] & kSBit) > 0; + absl::optional parsed_payload( + absl::in_place); + uint8_t fnri = rtp_payload.cdata()[0] & (kFBit | kNriMask); + uint8_t original_nal_type = rtp_payload.cdata()[1] & kTypeMask; + bool first_fragment = (rtp_payload.cdata()[1] & kSBit) > 0; NaluInfo nalu; nalu.type = original_nal_type; nalu.sps_id = -1; nalu.pps_id = -1; if (first_fragment) { - offset_ = 0; - length_ -= kNalHeaderSize; - absl::optional pps_id = PpsParser::ParsePpsIdFromSlice( - payload_data + 2 * kNalHeaderSize, length_ - kNalHeaderSize); + absl::optional pps_id = + PpsParser::ParsePpsIdFromSlice(rtp_payload.cdata() + 2 * kNalHeaderSize, + rtp_payload.size() - 2 * kNalHeaderSize); if (pps_id) { nalu.pps_id = *pps_id; } else { @@ -261,70 +262,55 @@ bool RtpDepacketizerH264::ParseFuaNalu( << static_cast(nalu.type); } uint8_t original_nal_header = fnri | original_nal_type; - modified_buffer_.reset(new rtc::Buffer()); - modified_buffer_->AppendData(payload_data + kNalHeaderSize, length_); - (*modified_buffer_)[0] = original_nal_header; + rtp_payload = + rtp_payload.Slice(kNalHeaderSize, rtp_payload.size() - kNalHeaderSize); + rtp_payload[0] = original_nal_header; + parsed_payload->video_payload = std::move(rtp_payload); } else { - offset_ = kFuAHeaderSize; - length_ -= kFuAHeaderSize; + parsed_payload->video_payload = + rtp_payload.Slice(kFuAHeaderSize, rtp_payload.size() - kFuAHeaderSize); } if (original_nal_type == H264::NaluType::kIdr) { - parsed_payload->video_header().frame_type = VideoFrameType::kVideoFrameKey; + parsed_payload->video_header.frame_type = VideoFrameType::kVideoFrameKey; } else { - parsed_payload->video_header().frame_type = - VideoFrameType::kVideoFrameDelta; + parsed_payload->video_header.frame_type = VideoFrameType::kVideoFrameDelta; } - parsed_payload->video_header().width = 0; - parsed_payload->video_header().height = 0; - parsed_payload->video_header().codec = kVideoCodecH264; - parsed_payload->video_header().simulcastIdx = 0; - parsed_payload->video_header().is_first_packet_in_frame = first_fragment; - auto& h264_header = absl::get( - parsed_payload->video_header().video_type_header); + parsed_payload->video_header.width = 0; + parsed_payload->video_header.height = 0; + parsed_payload->video_header.codec = kVideoCodecH264; + parsed_payload->video_header.simulcastIdx = 0; + parsed_payload->video_header.is_first_packet_in_frame = first_fragment; + auto& h264_header = parsed_payload->video_header.video_type_header + .emplace(); h264_header.packetization_type = kH264FuA; h264_header.nalu_type = original_nal_type; if (first_fragment) { h264_header.nalus[h264_header.nalus_length] = nalu; h264_header.nalus_length = 1; } - return true; + return parsed_payload; } -bool RtpDepacketizerH264::Parse(ParsedPayload* parsed_payload, - const uint8_t* payload_data, - size_t payload_data_length) { - RTC_CHECK(parsed_payload != nullptr); - if (payload_data_length == 0) { +} // namespace + +absl::optional +VideoRtpDepacketizerH264::Parse(rtc::CopyOnWriteBuffer rtp_payload) { + if (rtp_payload.size() == 0) { RTC_LOG(LS_ERROR) << "Empty payload."; - return false; + return absl::nullopt; } - offset_ = 0; - length_ = payload_data_length; - modified_buffer_.reset(); + uint8_t nal_type = rtp_payload.cdata()[0] & kTypeMask; - uint8_t nal_type = payload_data[0] & kTypeMask; - parsed_payload->video_header() - .video_type_header.emplace(); if (nal_type == H264::NaluType::kFuA) { // Fragmented NAL units (FU-A). - if (!ParseFuaNalu(parsed_payload, payload_data)) - return false; + return ParseFuaNalu(std::move(rtp_payload)); } else { // We handle STAP-A and single NALU's the same way here. The jitter buffer // will depacketize the STAP-A into NAL units later. - // TODO(sprang): Parse STAP-A offsets here and store in fragmentation vec. - if (!ProcessStapAOrSingleNalu(parsed_payload, payload_data)) - return false; + return ProcessStapAOrSingleNalu(std::move(rtp_payload)); } - - const uint8_t* payload = - modified_buffer_ ? modified_buffer_->data() : payload_data; - - parsed_payload->payload = payload + offset_; - parsed_payload->payload_length = length_; - return true; } } // namespace webrtc diff --git a/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h b/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h index 044ad035af..cbea860049 100644 --- a/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h +++ b/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h @@ -11,33 +11,17 @@ #ifndef MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_H264_H_ #define MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_H264_H_ -#include -#include - -#include - -#include "modules/rtp_rtcp/source/rtp_format.h" -#include "rtc_base/buffer.h" +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "rtc_base/copy_on_write_buffer.h" namespace webrtc { -class RtpDepacketizerH264 : public RtpDepacketizer { +class VideoRtpDepacketizerH264 : public VideoRtpDepacketizer { public: - RtpDepacketizerH264(); - ~RtpDepacketizerH264() override; + ~VideoRtpDepacketizerH264() override = default; - bool Parse(ParsedPayload* parsed_payload, - const uint8_t* payload_data, - size_t payload_data_length) override; - - private: - bool ParseFuaNalu(RtpDepacketizer::ParsedPayload* parsed_payload, - const uint8_t* payload_data); - bool ProcessStapAOrSingleNalu(RtpDepacketizer::ParsedPayload* parsed_payload, - const uint8_t* payload_data); - - size_t offset_; - size_t length_; - std::unique_ptr modified_buffer_; + absl::optional Parse( + rtc::CopyOnWriteBuffer rtp_payload) override; }; } // namespace webrtc diff --git a/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc b/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc index d4467aa279..d7e6147fd6 100644 --- a/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc +++ b/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc @@ -10,14 +10,16 @@ #include "modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h" -#include +#include #include +#include "absl/types/optional.h" #include "api/array_view.h" #include "common_video/h264/h264_common.h" #include "modules/include/module_common_types.h" #include "modules/rtp_rtcp/mocks/mock_rtp_rtcp.h" #include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/copy_on_write_buffer.h" #include "test/gmock.h" #include "test/gtest.h" @@ -47,67 +49,56 @@ enum NalDefs { kFBit = 0x80, kNriMask = 0x60, kTypeMask = 0x1F }; // Bit masks for FU (A and B) headers. enum FuDefs { kSBit = 0x80, kEBit = 0x40, kRBit = 0x20 }; -const uint8_t kOriginalSps[] = {kSps, 0x00, 0x00, 0x03, 0x03, - 0xF4, 0x05, 0x03, 0xC7, 0xC0}; -const uint8_t kRewrittenSps[] = {kSps, 0x00, 0x00, 0x03, 0x03, 0xF4, 0x05, 0x03, - 0xC7, 0xE0, 0x1B, 0x41, 0x10, 0x8D, 0x00}; -const uint8_t kIdrOne[] = {kIdr, 0xFF, 0x00, 0x00, 0x04}; -const uint8_t kIdrTwo[] = {kIdr, 0xFF, 0x00, 0x11}; +constexpr uint8_t kOriginalSps[] = {kSps, 0x00, 0x00, 0x03, 0x03, + 0xF4, 0x05, 0x03, 0xC7, 0xC0}; +constexpr uint8_t kRewrittenSps[] = {kSps, 0x00, 0x00, 0x03, 0x03, + 0xF4, 0x05, 0x03, 0xC7, 0xE0, + 0x1B, 0x41, 0x10, 0x8D, 0x00}; +constexpr uint8_t kIdrOne[] = {kIdr, 0xFF, 0x00, 0x00, 0x04}; +constexpr uint8_t kIdrTwo[] = {kIdr, 0xFF, 0x00, 0x11}; -struct H264ParsedPayload : public RtpDepacketizer::ParsedPayload { - RTPVideoHeaderH264& h264() { - return absl::get(video.video_type_header); - } -}; - -class RtpDepacketizerH264Test : public ::testing::Test { - protected: - RtpDepacketizerH264Test() - : depacketizer_(std::make_unique()) {} - - void ExpectPacket(H264ParsedPayload* parsed_payload, - const uint8_t* data, - size_t length) { - ASSERT_TRUE(parsed_payload != NULL); - EXPECT_THAT(std::vector( - parsed_payload->payload, - parsed_payload->payload + parsed_payload->payload_length), - ::testing::ElementsAreArray(data, length)); - } - - std::unique_ptr depacketizer_; -}; - -TEST_F(RtpDepacketizerH264Test, TestSingleNalu) { +TEST(VideoRtpDepacketizerH264Test, SingleNalu) { uint8_t packet[2] = {0x05, 0xFF}; // F=0, NRI=0, Type=5 (IDR). - H264ParsedPayload payload; + rtc::CopyOnWriteBuffer rtp_payload(packet); - ASSERT_TRUE(depacketizer_->Parse(&payload, packet, sizeof(packet))); - ExpectPacket(&payload, packet, sizeof(packet)); - EXPECT_EQ(VideoFrameType::kVideoFrameKey, payload.video_header().frame_type); - EXPECT_EQ(kVideoCodecH264, payload.video_header().codec); - EXPECT_TRUE(payload.video_header().is_first_packet_in_frame); - EXPECT_EQ(kH264SingleNalu, payload.h264().packetization_type); - EXPECT_EQ(kIdr, payload.h264().nalu_type); + VideoRtpDepacketizerH264 depacketizer; + absl::optional parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload, rtp_payload); + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(parsed->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + const RTPVideoHeaderH264& h264 = + absl::get(parsed->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264SingleNalu); + EXPECT_EQ(h264.nalu_type, kIdr); } -TEST_F(RtpDepacketizerH264Test, TestSingleNaluSpsWithResolution) { +TEST(VideoRtpDepacketizerH264Test, SingleNaluSpsWithResolution) { uint8_t packet[] = {kSps, 0x7A, 0x00, 0x1F, 0xBC, 0xD9, 0x40, 0x50, 0x05, 0xBA, 0x10, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x03, 0x2A, 0xE0, 0xF1, 0x83, 0x25}; - H264ParsedPayload payload; + rtc::CopyOnWriteBuffer rtp_payload(packet); - ASSERT_TRUE(depacketizer_->Parse(&payload, packet, sizeof(packet))); - ExpectPacket(&payload, packet, sizeof(packet)); - EXPECT_EQ(VideoFrameType::kVideoFrameKey, payload.video_header().frame_type); - EXPECT_EQ(kVideoCodecH264, payload.video_header().codec); - EXPECT_TRUE(payload.video_header().is_first_packet_in_frame); - EXPECT_EQ(kH264SingleNalu, payload.h264().packetization_type); - EXPECT_EQ(1280u, payload.video_header().width); - EXPECT_EQ(720u, payload.video_header().height); + VideoRtpDepacketizerH264 depacketizer; + absl::optional parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload, rtp_payload); + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(parsed->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + EXPECT_EQ(parsed->video_header.width, 1280u); + EXPECT_EQ(parsed->video_header.height, 720u); + const auto& h264 = + absl::get(parsed->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264SingleNalu); } -TEST_F(RtpDepacketizerH264Test, TestStapAKey) { +TEST(VideoRtpDepacketizerH264Test, StapAKey) { // clang-format off const NaluInfo kExpectedNalus[] = { {H264::kSps, 0, -1}, {H264::kPps, 1, 2}, @@ -124,29 +115,34 @@ TEST_F(RtpDepacketizerH264Test, TestStapAKey) { 0, 0xB, kExpectedNalus[2].type, 0x85, 0xB8, 0x0, 0x4, 0x0, 0x0, 0x13, 0x93, 0x12, 0x0}; // clang-format on + rtc::CopyOnWriteBuffer rtp_payload(packet); - H264ParsedPayload payload; - ASSERT_TRUE(depacketizer_->Parse(&payload, packet, sizeof(packet))); - ExpectPacket(&payload, packet, sizeof(packet)); - EXPECT_EQ(VideoFrameType::kVideoFrameKey, payload.video_header().frame_type); - EXPECT_EQ(kVideoCodecH264, payload.video_header().codec); - EXPECT_TRUE(payload.video_header().is_first_packet_in_frame); - const RTPVideoHeaderH264& h264 = payload.h264(); - EXPECT_EQ(kH264StapA, h264.packetization_type); + VideoRtpDepacketizerH264 depacketizer; + absl::optional parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload, rtp_payload); + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(parsed->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + const auto& h264 = + absl::get(parsed->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264StapA); // NALU type for aggregated packets is the type of the first packet only. - EXPECT_EQ(kSps, h264.nalu_type); - ASSERT_EQ(3u, h264.nalus_length); + EXPECT_EQ(h264.nalu_type, kSps); + ASSERT_EQ(h264.nalus_length, 3u); for (size_t i = 0; i < h264.nalus_length; ++i) { - EXPECT_EQ(kExpectedNalus[i].type, h264.nalus[i].type) + EXPECT_EQ(h264.nalus[i].type, kExpectedNalus[i].type) << "Failed parsing nalu " << i; - EXPECT_EQ(kExpectedNalus[i].sps_id, h264.nalus[i].sps_id) + EXPECT_EQ(h264.nalus[i].sps_id, kExpectedNalus[i].sps_id) << "Failed parsing nalu " << i; - EXPECT_EQ(kExpectedNalus[i].pps_id, h264.nalus[i].pps_id) + EXPECT_EQ(h264.nalus[i].pps_id, kExpectedNalus[i].pps_id) << "Failed parsing nalu " << i; } } -TEST_F(RtpDepacketizerH264Test, TestStapANaluSpsWithResolution) { +TEST(VideoRtpDepacketizerH264Test, StapANaluSpsWithResolution) { uint8_t packet[] = {kStapA, // F=0, NRI=0, Type=24. // Length (2 bytes), nal header, payload. 0x00, 0x19, kSps, 0x7A, 0x00, 0x1F, 0xBC, 0xD9, 0x40, @@ -154,45 +150,44 @@ TEST_F(RtpDepacketizerH264Test, TestStapANaluSpsWithResolution) { 0x00, 0x00, 0x03, 0x2A, 0xE0, 0xF1, 0x83, 0x25, 0x80, 0x00, 0x03, kIdr, 0xFF, 0x00, 0x00, 0x04, kIdr, 0xFF, 0x00, 0x11}; + rtc::CopyOnWriteBuffer rtp_payload(packet); - H264ParsedPayload payload; + VideoRtpDepacketizerH264 depacketizer; + absl::optional parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); - ASSERT_TRUE(depacketizer_->Parse(&payload, packet, sizeof(packet))); - ExpectPacket(&payload, packet, sizeof(packet)); - EXPECT_EQ(VideoFrameType::kVideoFrameKey, payload.video_header().frame_type); - EXPECT_EQ(kVideoCodecH264, payload.video_header().codec); - EXPECT_TRUE(payload.video_header().is_first_packet_in_frame); - EXPECT_EQ(kH264StapA, payload.h264().packetization_type); - EXPECT_EQ(1280u, payload.video_header().width); - EXPECT_EQ(720u, payload.video_header().height); + EXPECT_EQ(parsed->video_payload, rtp_payload); + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(parsed->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + EXPECT_EQ(parsed->video_header.width, 1280u); + EXPECT_EQ(parsed->video_header.height, 720u); + const auto& h264 = + absl::get(parsed->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264StapA); } -TEST_F(RtpDepacketizerH264Test, TestEmptyStapARejected) { +TEST(VideoRtpDepacketizerH264Test, EmptyStapARejected) { uint8_t lone_empty_packet[] = {kStapA, 0x00, 0x00}; - uint8_t leading_empty_packet[] = {kStapA, 0x00, 0x00, 0x00, 0x04, kIdr, 0xFF, 0x00, 0x11}; - uint8_t middle_empty_packet[] = {kStapA, 0x00, 0x03, kIdr, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x04, kIdr, 0xFF, 0x00, 0x11}; - uint8_t trailing_empty_packet[] = {kStapA, 0x00, 0x03, kIdr, 0xFF, 0x00, 0x00, 0x00}; - H264ParsedPayload payload; - - EXPECT_FALSE(depacketizer_->Parse(&payload, lone_empty_packet, - sizeof(lone_empty_packet))); - EXPECT_FALSE(depacketizer_->Parse(&payload, leading_empty_packet, - sizeof(leading_empty_packet))); - EXPECT_FALSE(depacketizer_->Parse(&payload, middle_empty_packet, - sizeof(middle_empty_packet))); - EXPECT_FALSE(depacketizer_->Parse(&payload, trailing_empty_packet, - sizeof(trailing_empty_packet))); + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(lone_empty_packet))); + EXPECT_FALSE( + depacketizer.Parse(rtc::CopyOnWriteBuffer(leading_empty_packet))); + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(middle_empty_packet))); + EXPECT_FALSE( + depacketizer.Parse(rtc::CopyOnWriteBuffer(trailing_empty_packet))); } -TEST_F(RtpDepacketizerH264Test, DepacketizeWithRewriting) { - rtc::Buffer in_buffer; +TEST(VideoRtpDepacketizerH264Test, DepacketizeWithRewriting) { + rtc::CopyOnWriteBuffer in_buffer; rtc::Buffer out_buffer; uint8_t kHeader[2] = {kStapA}; @@ -218,20 +213,16 @@ TEST_F(RtpDepacketizerH264Test, DepacketizeWithRewriting) { out_buffer.AppendData(kHeader, 2); out_buffer.AppendData(kIdrTwo); - H264ParsedPayload payload; - EXPECT_TRUE( - depacketizer_->Parse(&payload, in_buffer.data(), in_buffer.size())); - - std::vector expected_packet_payload( - out_buffer.data(), &out_buffer.data()[out_buffer.size()]); - - EXPECT_THAT( - expected_packet_payload, - ::testing::ElementsAreArray(payload.payload, payload.payload_length)); + VideoRtpDepacketizerH264 depacketizer; + auto parsed = depacketizer.Parse(in_buffer); + ASSERT_TRUE(parsed); + EXPECT_THAT(rtc::MakeArrayView(parsed->video_payload.cdata(), + parsed->video_payload.size()), + ElementsAreArray(out_buffer)); } -TEST_F(RtpDepacketizerH264Test, DepacketizeWithDoubleRewriting) { - rtc::Buffer in_buffer; +TEST(VideoRtpDepacketizerH264Test, DepacketizeWithDoubleRewriting) { + rtc::CopyOnWriteBuffer in_buffer; rtc::Buffer out_buffer; uint8_t kHeader[2] = {kStapA}; @@ -265,37 +256,42 @@ TEST_F(RtpDepacketizerH264Test, DepacketizeWithDoubleRewriting) { out_buffer.AppendData(kHeader, 2); out_buffer.AppendData(kIdrTwo); - H264ParsedPayload payload; - EXPECT_TRUE( - depacketizer_->Parse(&payload, in_buffer.data(), in_buffer.size())); - + VideoRtpDepacketizerH264 depacketizer; + auto parsed = depacketizer.Parse(in_buffer); + ASSERT_TRUE(parsed); std::vector expected_packet_payload( out_buffer.data(), &out_buffer.data()[out_buffer.size()]); - - EXPECT_THAT( - expected_packet_payload, - ::testing::ElementsAreArray(payload.payload, payload.payload_length)); + EXPECT_THAT(rtc::MakeArrayView(parsed->video_payload.cdata(), + parsed->video_payload.size()), + ElementsAreArray(out_buffer)); } -TEST_F(RtpDepacketizerH264Test, TestStapADelta) { +TEST(VideoRtpDepacketizerH264Test, StapADelta) { uint8_t packet[16] = {kStapA, // F=0, NRI=0, Type=24. // Length, nal header, payload. 0, 0x02, kSlice, 0xFF, 0, 0x03, kSlice, 0xFF, 0x00, 0, 0x04, kSlice, 0xFF, 0x00, 0x11}; - H264ParsedPayload payload; + rtc::CopyOnWriteBuffer rtp_payload(packet); - ASSERT_TRUE(depacketizer_->Parse(&payload, packet, sizeof(packet))); - ExpectPacket(&payload, packet, sizeof(packet)); - EXPECT_EQ(VideoFrameType::kVideoFrameDelta, - payload.video_header().frame_type); - EXPECT_EQ(kVideoCodecH264, payload.video_header().codec); - EXPECT_TRUE(payload.video_header().is_first_packet_in_frame); - EXPECT_EQ(kH264StapA, payload.h264().packetization_type); + VideoRtpDepacketizerH264 depacketizer; + absl::optional parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload.size(), rtp_payload.size()); + EXPECT_EQ(parsed->video_payload.cdata(), rtp_payload.cdata()); + + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameDelta); + EXPECT_EQ(parsed->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + const RTPVideoHeaderH264& h264 = + absl::get(parsed->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264StapA); // NALU type for aggregated packets is the type of the first packet only. - EXPECT_EQ(kSlice, payload.h264().nalu_type); + EXPECT_EQ(h264.nalu_type, kSlice); } -TEST_F(RtpDepacketizerH264Test, TestFuA) { +TEST(VideoRtpDepacketizerH264Test, FuA) { // clang-format off uint8_t packet1[] = { kFuA, // F=0, NRI=0, Type=28. @@ -320,107 +316,115 @@ TEST_F(RtpDepacketizerH264Test, TestFuA) { }; const uint8_t kExpected3[] = {0x03}; - H264ParsedPayload payload; - + VideoRtpDepacketizerH264 depacketizer; + absl::optional parsed1 = + depacketizer.Parse(rtc::CopyOnWriteBuffer(packet1)); + ASSERT_TRUE(parsed1); // We expect that the first packet is one byte shorter since the FU-A header // has been replaced by the original nal header. - ASSERT_TRUE(depacketizer_->Parse(&payload, packet1, sizeof(packet1))); - ExpectPacket(&payload, kExpected1, sizeof(kExpected1)); - EXPECT_EQ(VideoFrameType::kVideoFrameKey, payload.video_header().frame_type); - EXPECT_EQ(kVideoCodecH264, payload.video_header().codec); - EXPECT_TRUE(payload.video_header().is_first_packet_in_frame); - const RTPVideoHeaderH264& h264 = payload.h264(); - EXPECT_EQ(kH264FuA, h264.packetization_type); - EXPECT_EQ(kIdr, h264.nalu_type); - ASSERT_EQ(1u, h264.nalus_length); - EXPECT_EQ(static_cast(kIdr), h264.nalus[0].type); - EXPECT_EQ(-1, h264.nalus[0].sps_id); - EXPECT_EQ(0, h264.nalus[0].pps_id); + EXPECT_THAT(rtc::MakeArrayView(parsed1->video_payload.cdata(), + parsed1->video_payload.size()), + ElementsAreArray(kExpected1)); + EXPECT_EQ(parsed1->video_header.frame_type, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(parsed1->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed1->video_header.is_first_packet_in_frame); + { + const RTPVideoHeaderH264& h264 = + absl::get(parsed1->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264FuA); + EXPECT_EQ(h264.nalu_type, kIdr); + ASSERT_EQ(h264.nalus_length, 1u); + EXPECT_EQ(h264.nalus[0].type, static_cast(kIdr)); + EXPECT_EQ(h264.nalus[0].sps_id, -1); + EXPECT_EQ(h264.nalus[0].pps_id, 0); + } // Following packets will be 2 bytes shorter since they will only be appended // onto the first packet. - payload = H264ParsedPayload(); - ASSERT_TRUE(depacketizer_->Parse(&payload, packet2, sizeof(packet2))); - ExpectPacket(&payload, kExpected2, sizeof(kExpected2)); - EXPECT_EQ(VideoFrameType::kVideoFrameKey, payload.video_header().frame_type); - EXPECT_EQ(kVideoCodecH264, payload.video_header().codec); - EXPECT_FALSE(payload.video_header().is_first_packet_in_frame); + auto parsed2 = depacketizer.Parse(rtc::CopyOnWriteBuffer(packet2)); + EXPECT_THAT(rtc::MakeArrayView(parsed2->video_payload.cdata(), + parsed2->video_payload.size()), + ElementsAreArray(kExpected2)); + EXPECT_FALSE(parsed2->video_header.is_first_packet_in_frame); + EXPECT_EQ(parsed2->video_header.codec, kVideoCodecH264); { - const RTPVideoHeaderH264& h264 = payload.h264(); - EXPECT_EQ(kH264FuA, h264.packetization_type); - EXPECT_EQ(kIdr, h264.nalu_type); + const RTPVideoHeaderH264& h264 = + absl::get(parsed2->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264FuA); + EXPECT_EQ(h264.nalu_type, kIdr); // NALU info is only expected for the first FU-A packet. - EXPECT_EQ(0u, h264.nalus_length); + EXPECT_EQ(h264.nalus_length, 0u); } - payload = H264ParsedPayload(); - ASSERT_TRUE(depacketizer_->Parse(&payload, packet3, sizeof(packet3))); - ExpectPacket(&payload, kExpected3, sizeof(kExpected3)); - EXPECT_EQ(VideoFrameType::kVideoFrameKey, payload.video_header().frame_type); - EXPECT_EQ(kVideoCodecH264, payload.video_header().codec); - EXPECT_FALSE(payload.video_header().is_first_packet_in_frame); + auto parsed3 = depacketizer.Parse(rtc::CopyOnWriteBuffer(packet3)); + EXPECT_THAT(rtc::MakeArrayView(parsed3->video_payload.cdata(), + parsed3->video_payload.size()), + ElementsAreArray(kExpected3)); + EXPECT_FALSE(parsed3->video_header.is_first_packet_in_frame); + EXPECT_EQ(parsed3->video_header.codec, kVideoCodecH264); { - const RTPVideoHeaderH264& h264 = payload.h264(); - EXPECT_EQ(kH264FuA, h264.packetization_type); - EXPECT_EQ(kIdr, h264.nalu_type); + const RTPVideoHeaderH264& h264 = + absl::get(parsed3->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264FuA); + EXPECT_EQ(h264.nalu_type, kIdr); // NALU info is only expected for the first FU-A packet. - ASSERT_EQ(0u, h264.nalus_length); + ASSERT_EQ(h264.nalus_length, 0u); } } -TEST_F(RtpDepacketizerH264Test, TestEmptyPayload) { - // Using a wild pointer to crash on accesses from inside the depacketizer. - uint8_t* garbage_ptr = reinterpret_cast(0x4711); - H264ParsedPayload payload; - EXPECT_FALSE(depacketizer_->Parse(&payload, garbage_ptr, 0)); +TEST(VideoRtpDepacketizerH264Test, EmptyPayload) { + rtc::CopyOnWriteBuffer empty; + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(empty)); } -TEST_F(RtpDepacketizerH264Test, TestTruncatedFuaNalu) { +TEST(VideoRtpDepacketizerH264Test, TruncatedFuaNalu) { const uint8_t kPayload[] = {0x9c}; - H264ParsedPayload payload; - EXPECT_FALSE(depacketizer_->Parse(&payload, kPayload, sizeof(kPayload))); + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload))); } -TEST_F(RtpDepacketizerH264Test, TestTruncatedSingleStapANalu) { +TEST(VideoRtpDepacketizerH264Test, TruncatedSingleStapANalu) { const uint8_t kPayload[] = {0xd8, 0x27}; - H264ParsedPayload payload; - EXPECT_FALSE(depacketizer_->Parse(&payload, kPayload, sizeof(kPayload))); + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload))); } -TEST_F(RtpDepacketizerH264Test, TestStapAPacketWithTruncatedNalUnits) { +TEST(VideoRtpDepacketizerH264Test, StapAPacketWithTruncatedNalUnits) { const uint8_t kPayload[] = {0x58, 0xCB, 0xED, 0xDF}; - H264ParsedPayload payload; - EXPECT_FALSE(depacketizer_->Parse(&payload, kPayload, sizeof(kPayload))); + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload))); } -TEST_F(RtpDepacketizerH264Test, TestTruncationJustAfterSingleStapANalu) { +TEST(VideoRtpDepacketizerH264Test, TruncationJustAfterSingleStapANalu) { const uint8_t kPayload[] = {0x38, 0x27, 0x27}; - H264ParsedPayload payload; - EXPECT_FALSE(depacketizer_->Parse(&payload, kPayload, sizeof(kPayload))); + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload))); } -TEST_F(RtpDepacketizerH264Test, TestShortSpsPacket) { +TEST(VideoRtpDepacketizerH264Test, ShortSpsPacket) { const uint8_t kPayload[] = {0x27, 0x80, 0x00}; - H264ParsedPayload payload; - EXPECT_TRUE(depacketizer_->Parse(&payload, kPayload, sizeof(kPayload))); + VideoRtpDepacketizerH264 depacketizer; + EXPECT_TRUE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload))); } -TEST_F(RtpDepacketizerH264Test, TestSeiPacket) { +TEST(VideoRtpDepacketizerH264Test, SeiPacket) { const uint8_t kPayload[] = { kSei, // F=0, NRI=0, Type=6. 0x03, 0x03, 0x03, 0x03 // Payload. }; - H264ParsedPayload payload; - ASSERT_TRUE(depacketizer_->Parse(&payload, kPayload, sizeof(kPayload))); - const RTPVideoHeaderH264& h264 = payload.h264(); - EXPECT_EQ(VideoFrameType::kVideoFrameDelta, - payload.video_header().frame_type); - EXPECT_EQ(kH264SingleNalu, h264.packetization_type); - EXPECT_EQ(kSei, h264.nalu_type); - ASSERT_EQ(1u, h264.nalus_length); - EXPECT_EQ(static_cast(kSei), h264.nalus[0].type); - EXPECT_EQ(-1, h264.nalus[0].sps_id); - EXPECT_EQ(-1, h264.nalus[0].pps_id); + VideoRtpDepacketizerH264 depacketizer; + auto parsed = depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload)); + ASSERT_TRUE(parsed); + const RTPVideoHeaderH264& h264 = + absl::get(parsed->video_header.video_type_header); + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameDelta); + EXPECT_EQ(h264.packetization_type, kH264SingleNalu); + EXPECT_EQ(h264.nalu_type, kSei); + ASSERT_EQ(h264.nalus_length, 1u); + EXPECT_EQ(h264.nalus[0].type, static_cast(kSei)); + EXPECT_EQ(h264.nalus[0].sps_id, -1); + EXPECT_EQ(h264.nalus[0].pps_id, -1); } } // namespace diff --git a/test/fuzzers/h264_depacketizer_fuzzer.cc b/test/fuzzers/h264_depacketizer_fuzzer.cc index 9b6455b332..97127228ed 100644 --- a/test/fuzzers/h264_depacketizer_fuzzer.cc +++ b/test/fuzzers/h264_depacketizer_fuzzer.cc @@ -7,14 +7,13 @@ * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ -#include "modules/rtp_rtcp/source/rtp_format_h264.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h" namespace webrtc { void FuzzOneInput(const uint8_t* data, size_t size) { if (size > 200000) return; - RtpDepacketizerH264 depacketizer; - RtpDepacketizer::ParsedPayload parsed_payload; - depacketizer.Parse(&parsed_payload, data, size); + VideoRtpDepacketizerH264 depacketizer; + depacketizer.Parse(rtc::CopyOnWriteBuffer(data, size)); } } // namespace webrtc