
This is a partial revert of https://webrtc-review.googlesource.com/c/src/+/130101. The KeyFrameRequestSender argument is added back to the constructor of RtpVideoStreamReceiver. It is optional; if a null pointer is passed, key frame requests are sent via the internal RtpRtcp module, and this is how the class is used by VideoReceiveStream. An injectable KeyFrameRequestSender is useful for tests, for downstream applications that want to route key frame requests elsewhere, and should also aid later migration to RtcpTransciever. Bug: None Change-Id: Idf9baeed21570625ad74e9afbe38f7ea5bf79feb Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/139107 Commit-Queue: Niels Moller <nisse@webrtc.org> Reviewed-by: Åsa Persson <asapersson@webrtc.org> Cr-Commit-Position: refs/heads/master@{#28102}
878 lines
34 KiB
C++
878 lines
34 KiB
C++
/*
|
|
* Copyright 2017 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 "test/gmock.h"
|
|
#include "test/gtest.h"
|
|
|
|
#include "absl/memory/memory.h"
|
|
#include "api/video/video_codec_type.h"
|
|
#include "api/video/video_frame_type.h"
|
|
#include "common_video/h264/h264_common.h"
|
|
#include "media/base/media_constants.h"
|
|
#include "modules/rtp_rtcp/source/rtp_format.h"
|
|
#include "modules/rtp_rtcp/source/rtp_generic_frame_descriptor.h"
|
|
#include "modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.h"
|
|
#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
|
|
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
|
|
#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
|
|
#include "modules/utility/include/process_thread.h"
|
|
#include "modules/video_coding/frame_object.h"
|
|
#include "modules/video_coding/include/video_coding_defines.h"
|
|
#include "modules/video_coding/packet.h"
|
|
#include "modules/video_coding/rtp_frame_reference_finder.h"
|
|
#include "rtc_base/byte_buffer.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "system_wrappers/include/clock.h"
|
|
#include "system_wrappers/include/field_trial.h"
|
|
#include "test/field_trial.h"
|
|
#include "video/rtp_video_stream_receiver.h"
|
|
|
|
using ::testing::_;
|
|
using ::testing::Invoke;
|
|
using ::testing::Values;
|
|
|
|
namespace webrtc {
|
|
|
|
namespace {
|
|
|
|
const uint8_t kH264StartCode[] = {0x00, 0x00, 0x00, 0x01};
|
|
|
|
class MockTransport : public Transport {
|
|
public:
|
|
MOCK_METHOD3(SendRtp,
|
|
bool(const uint8_t* packet,
|
|
size_t length,
|
|
const PacketOptions& options));
|
|
MOCK_METHOD2(SendRtcp, bool(const uint8_t* packet, size_t length));
|
|
};
|
|
|
|
class MockNackSender : public NackSender {
|
|
public:
|
|
MOCK_METHOD1(SendNack, void(const std::vector<uint16_t>& sequence_numbers));
|
|
};
|
|
|
|
class MockKeyFrameRequestSender : public KeyFrameRequestSender {
|
|
public:
|
|
MOCK_METHOD0(RequestKeyFrame, void());
|
|
};
|
|
|
|
class MockOnCompleteFrameCallback
|
|
: public video_coding::OnCompleteFrameCallback {
|
|
public:
|
|
MockOnCompleteFrameCallback() : buffer_(rtc::ByteBuffer::ORDER_NETWORK) {}
|
|
|
|
MOCK_METHOD1(DoOnCompleteFrame, void(video_coding::EncodedFrame* frame));
|
|
MOCK_METHOD1(DoOnCompleteFrameFailNullptr,
|
|
void(video_coding::EncodedFrame* frame));
|
|
MOCK_METHOD1(DoOnCompleteFrameFailLength,
|
|
void(video_coding::EncodedFrame* frame));
|
|
MOCK_METHOD1(DoOnCompleteFrameFailBitstream,
|
|
void(video_coding::EncodedFrame* frame));
|
|
void OnCompleteFrame(std::unique_ptr<video_coding::EncodedFrame> frame) {
|
|
if (!frame) {
|
|
DoOnCompleteFrameFailNullptr(nullptr);
|
|
return;
|
|
}
|
|
EXPECT_EQ(buffer_.Length(), frame->size());
|
|
if (buffer_.Length() != frame->size()) {
|
|
DoOnCompleteFrameFailLength(frame.get());
|
|
return;
|
|
}
|
|
if (frame->size() != buffer_.Length() ||
|
|
memcmp(buffer_.Data(), frame->data(), buffer_.Length()) != 0) {
|
|
DoOnCompleteFrameFailBitstream(frame.get());
|
|
return;
|
|
}
|
|
DoOnCompleteFrame(frame.get());
|
|
}
|
|
|
|
void ClearExpectedBitstream() { buffer_.Clear(); }
|
|
|
|
void AppendExpectedBitstream(const uint8_t data[], size_t size_in_bytes) {
|
|
// TODO(Johan): Let rtc::ByteBuffer handle uint8_t* instead of char*.
|
|
buffer_.WriteBytes(reinterpret_cast<const char*>(data), size_in_bytes);
|
|
}
|
|
rtc::ByteBufferWriter buffer_;
|
|
};
|
|
|
|
class MockRtpPacketSink : public RtpPacketSinkInterface {
|
|
public:
|
|
MOCK_METHOD1(OnRtpPacket, void(const RtpPacketReceived&));
|
|
};
|
|
|
|
constexpr uint32_t kSsrc = 111;
|
|
constexpr uint16_t kSequenceNumber = 222;
|
|
std::unique_ptr<RtpPacketReceived> CreateRtpPacketReceived(
|
|
uint32_t ssrc = kSsrc,
|
|
uint16_t sequence_number = kSequenceNumber) {
|
|
auto packet = absl::make_unique<RtpPacketReceived>();
|
|
packet->SetSsrc(ssrc);
|
|
packet->SetSequenceNumber(sequence_number);
|
|
return packet;
|
|
}
|
|
|
|
MATCHER_P(SamePacketAs, other, "") {
|
|
return arg.Ssrc() == other.Ssrc() &&
|
|
arg.SequenceNumber() == other.SequenceNumber();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class RtpVideoStreamReceiverTest : public ::testing::Test {
|
|
public:
|
|
RtpVideoStreamReceiverTest() : RtpVideoStreamReceiverTest("") {}
|
|
explicit RtpVideoStreamReceiverTest(std::string field_trials)
|
|
: override_field_trials_(field_trials),
|
|
config_(CreateConfig()),
|
|
process_thread_(ProcessThread::Create("TestThread")) {}
|
|
|
|
void SetUp() {
|
|
rtp_receive_statistics_ =
|
|
absl::WrapUnique(ReceiveStatistics::Create(Clock::GetRealTimeClock()));
|
|
rtp_video_stream_receiver_ = absl::make_unique<RtpVideoStreamReceiver>(
|
|
Clock::GetRealTimeClock(), &mock_transport_, nullptr, nullptr, &config_,
|
|
rtp_receive_statistics_.get(), nullptr, process_thread_.get(),
|
|
&mock_nack_sender_, &mock_key_frame_request_sender_,
|
|
&mock_on_complete_frame_callback_, nullptr);
|
|
}
|
|
|
|
RTPVideoHeader GetDefaultH264VideoHeader() {
|
|
RTPVideoHeader video_header;
|
|
video_header.codec = kVideoCodecH264;
|
|
video_header.video_type_header.emplace<RTPVideoHeaderH264>();
|
|
return video_header;
|
|
}
|
|
|
|
// TODO(Johan): refactor h264_sps_pps_tracker_unittests.cc to avoid duplicate
|
|
// code.
|
|
void AddSps(RTPVideoHeader* video_header,
|
|
uint8_t sps_id,
|
|
std::vector<uint8_t>* data) {
|
|
NaluInfo info;
|
|
info.type = H264::NaluType::kSps;
|
|
info.sps_id = sps_id;
|
|
info.pps_id = -1;
|
|
data->push_back(H264::NaluType::kSps);
|
|
data->push_back(sps_id);
|
|
auto& h264 = absl::get<RTPVideoHeaderH264>(video_header->video_type_header);
|
|
h264.nalus[h264.nalus_length++] = info;
|
|
}
|
|
|
|
void AddPps(RTPVideoHeader* video_header,
|
|
uint8_t sps_id,
|
|
uint8_t pps_id,
|
|
std::vector<uint8_t>* data) {
|
|
NaluInfo info;
|
|
info.type = H264::NaluType::kPps;
|
|
info.sps_id = sps_id;
|
|
info.pps_id = pps_id;
|
|
data->push_back(H264::NaluType::kPps);
|
|
data->push_back(pps_id);
|
|
auto& h264 = absl::get<RTPVideoHeaderH264>(video_header->video_type_header);
|
|
h264.nalus[h264.nalus_length++] = info;
|
|
}
|
|
|
|
void AddIdr(RTPVideoHeader* video_header, int pps_id) {
|
|
NaluInfo info;
|
|
info.type = H264::NaluType::kIdr;
|
|
info.sps_id = -1;
|
|
info.pps_id = pps_id;
|
|
auto& h264 = absl::get<RTPVideoHeaderH264>(video_header->video_type_header);
|
|
h264.nalus[h264.nalus_length++] = info;
|
|
}
|
|
|
|
protected:
|
|
static VideoReceiveStream::Config CreateConfig() {
|
|
VideoReceiveStream::Config config(nullptr);
|
|
config.rtp.remote_ssrc = 1111;
|
|
config.rtp.local_ssrc = 2222;
|
|
return config;
|
|
}
|
|
|
|
const webrtc::test::ScopedFieldTrials override_field_trials_;
|
|
VideoReceiveStream::Config config_;
|
|
MockNackSender mock_nack_sender_;
|
|
MockKeyFrameRequestSender mock_key_frame_request_sender_;
|
|
MockTransport mock_transport_;
|
|
MockOnCompleteFrameCallback mock_on_complete_frame_callback_;
|
|
std::unique_ptr<ProcessThread> process_thread_;
|
|
std::unique_ptr<ReceiveStatistics> rtp_receive_statistics_;
|
|
std::unique_ptr<RtpVideoStreamReceiver> rtp_video_stream_receiver_;
|
|
};
|
|
|
|
TEST_F(RtpVideoStreamReceiverTest, CacheColorSpaceFromLastPacketOfKeyframe) {
|
|
// Test that color space is cached from the last packet of a key frame and
|
|
// that it's not reset by padding packets without color space.
|
|
constexpr int kPayloadType = 99;
|
|
const ColorSpace kColorSpace(
|
|
ColorSpace::PrimaryID::kFILM, ColorSpace::TransferID::kBT2020_12,
|
|
ColorSpace::MatrixID::kBT2020_NCL, ColorSpace::RangeID::kFull);
|
|
const std::vector<uint8_t> kKeyFramePayload = {0, 1, 2, 3, 4, 5,
|
|
6, 7, 8, 9, 10};
|
|
const std::vector<uint8_t> kDeltaFramePayload = {0, 1, 2, 3, 4};
|
|
|
|
// Anonymous helper class that generates received packets.
|
|
class {
|
|
public:
|
|
void SetPayload(const std::vector<uint8_t>& payload,
|
|
VideoFrameType video_frame_type) {
|
|
video_frame_type_ = video_frame_type;
|
|
RtpPacketizer::PayloadSizeLimits pay_load_size_limits;
|
|
// Reduce max payload length to make sure the key frame generates two
|
|
// packets.
|
|
pay_load_size_limits.max_payload_len = 8;
|
|
RTPVideoHeader rtp_video_header;
|
|
RTPVideoHeaderVP9 rtp_video_header_vp9;
|
|
rtp_video_header_vp9.InitRTPVideoHeaderVP9();
|
|
rtp_video_header_vp9.inter_pic_predicted =
|
|
(video_frame_type == VideoFrameType::kVideoFrameDelta);
|
|
rtp_video_header.video_type_header = rtp_video_header_vp9;
|
|
rtp_packetizer_ = RtpPacketizer::Create(
|
|
kVideoCodecVP9, rtc::MakeArrayView(payload.data(), payload.size()),
|
|
pay_load_size_limits, rtp_video_header, video_frame_type, nullptr);
|
|
}
|
|
|
|
size_t NumPackets() { return rtp_packetizer_->NumPackets(); }
|
|
void SetColorSpace(const ColorSpace& color_space) {
|
|
color_space_ = color_space;
|
|
}
|
|
|
|
RtpPacketReceived NextPacket() {
|
|
RtpHeaderExtensionMap extension_map;
|
|
extension_map.Register<ColorSpaceExtension>(1);
|
|
RtpPacketToSend packet_to_send(&extension_map);
|
|
packet_to_send.SetSequenceNumber(sequence_number_++);
|
|
packet_to_send.SetSsrc(kSsrc);
|
|
packet_to_send.SetPayloadType(kPayloadType);
|
|
bool include_color_space =
|
|
(rtp_packetizer_->NumPackets() == 1u &&
|
|
video_frame_type_ == VideoFrameType::kVideoFrameKey);
|
|
if (include_color_space) {
|
|
EXPECT_TRUE(
|
|
packet_to_send.SetExtension<ColorSpaceExtension>(color_space_));
|
|
}
|
|
rtp_packetizer_->NextPacket(&packet_to_send);
|
|
|
|
RtpPacketReceived received_packet(&extension_map);
|
|
received_packet.Parse(packet_to_send.data(), packet_to_send.size());
|
|
return received_packet;
|
|
}
|
|
|
|
private:
|
|
uint16_t sequence_number_ = 0;
|
|
VideoFrameType video_frame_type_;
|
|
ColorSpace color_space_;
|
|
std::unique_ptr<RtpPacketizer> rtp_packetizer_;
|
|
} received_packet_generator;
|
|
received_packet_generator.SetColorSpace(kColorSpace);
|
|
|
|
// Prepare the receiver for VP9.
|
|
VideoCodec codec;
|
|
codec.plType = kPayloadType;
|
|
codec.codecType = kVideoCodecVP9;
|
|
std::map<std::string, std::string> codec_params;
|
|
rtp_video_stream_receiver_->AddReceiveCodec(codec, codec_params,
|
|
/*raw_payload=*/false);
|
|
|
|
// Generate key frame packets.
|
|
received_packet_generator.SetPayload(kKeyFramePayload,
|
|
VideoFrameType::kVideoFrameKey);
|
|
EXPECT_EQ(received_packet_generator.NumPackets(), 2u);
|
|
RtpPacketReceived key_frame_packet1 = received_packet_generator.NextPacket();
|
|
RtpPacketReceived key_frame_packet2 = received_packet_generator.NextPacket();
|
|
|
|
// Generate delta frame packet.
|
|
received_packet_generator.SetPayload(kDeltaFramePayload,
|
|
VideoFrameType::kVideoFrameDelta);
|
|
EXPECT_EQ(received_packet_generator.NumPackets(), 1u);
|
|
RtpPacketReceived delta_frame_packet = received_packet_generator.NextPacket();
|
|
|
|
rtp_video_stream_receiver_->StartReceive();
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(
|
|
kKeyFramePayload.data(), kKeyFramePayload.size());
|
|
|
|
// Send the key frame and expect a callback with color space information.
|
|
EXPECT_FALSE(key_frame_packet1.GetExtension<ColorSpaceExtension>());
|
|
EXPECT_TRUE(key_frame_packet2.GetExtension<ColorSpaceExtension>());
|
|
rtp_video_stream_receiver_->OnRtpPacket(key_frame_packet1);
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_))
|
|
.WillOnce(Invoke([kColorSpace](video_coding::EncodedFrame* frame) {
|
|
ASSERT_TRUE(frame->EncodedImage().ColorSpace());
|
|
EXPECT_EQ(*frame->EncodedImage().ColorSpace(), kColorSpace);
|
|
}));
|
|
rtp_video_stream_receiver_->OnRtpPacket(key_frame_packet2);
|
|
// Resend the first key frame packet to simulate padding for example.
|
|
rtp_video_stream_receiver_->OnRtpPacket(key_frame_packet1);
|
|
|
|
mock_on_complete_frame_callback_.ClearExpectedBitstream();
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(
|
|
kDeltaFramePayload.data(), kDeltaFramePayload.size());
|
|
|
|
// Expect delta frame to have color space set even though color space not
|
|
// included in the RTP packet.
|
|
EXPECT_FALSE(delta_frame_packet.GetExtension<ColorSpaceExtension>());
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_))
|
|
.WillOnce(Invoke([kColorSpace](video_coding::EncodedFrame* frame) {
|
|
ASSERT_TRUE(frame->EncodedImage().ColorSpace());
|
|
EXPECT_EQ(*frame->EncodedImage().ColorSpace(), kColorSpace);
|
|
}));
|
|
rtp_video_stream_receiver_->OnRtpPacket(delta_frame_packet);
|
|
}
|
|
|
|
TEST_F(RtpVideoStreamReceiverTest, GenericKeyFrame) {
|
|
RTPHeader rtp_header;
|
|
RTPVideoHeader video_header;
|
|
const std::vector<uint8_t> data({1, 2, 3, 4});
|
|
rtp_header.sequenceNumber = 1;
|
|
video_header.is_first_packet_in_frame = true;
|
|
video_header.is_last_packet_in_frame = true;
|
|
video_header.codec = kVideoCodecGeneric;
|
|
video_header.frame_type = VideoFrameType::kVideoFrameKey;
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data(),
|
|
data.size());
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
data.data(), data.size(), rtp_header, video_header, absl::nullopt, false);
|
|
}
|
|
|
|
TEST_F(RtpVideoStreamReceiverTest, NoInfiniteRecursionOnEncapsulatedRedPacket) {
|
|
const uint8_t kRedPayloadType = 125;
|
|
VideoCodec codec;
|
|
codec.plType = kRedPayloadType;
|
|
rtp_video_stream_receiver_->AddReceiveCodec(codec, {}, /*raw_payload=*/false);
|
|
const std::vector<uint8_t> data({
|
|
0x80, // RTP version.
|
|
kRedPayloadType, // Payload type.
|
|
0, 0, 0, 0, 0, 0, // Don't care.
|
|
0, 0, 0x4, 0x57, // SSRC
|
|
kRedPayloadType, // RED header.
|
|
0, 0, 0, 0, 0 // Don't care.
|
|
});
|
|
RtpPacketReceived packet;
|
|
EXPECT_TRUE(packet.Parse(data.data(), data.size()));
|
|
rtp_video_stream_receiver_->StartReceive();
|
|
rtp_video_stream_receiver_->OnRtpPacket(packet);
|
|
}
|
|
|
|
TEST_F(RtpVideoStreamReceiverTest,
|
|
DropsPacketWithRedPayloadTypeAndEmptyPayload) {
|
|
const uint8_t kRedPayloadType = 125;
|
|
config_.rtp.red_payload_type = kRedPayloadType;
|
|
SetUp(); // re-create rtp_video_stream_receiver with red payload type.
|
|
// clang-format off
|
|
const uint8_t data[] = {
|
|
0x80, // RTP version.
|
|
kRedPayloadType, // Payload type.
|
|
0, 0, 0, 0, 0, 0, // Don't care.
|
|
0, 0, 0x4, 0x57, // SSRC
|
|
// Empty rtp payload.
|
|
};
|
|
// clang-format on
|
|
RtpPacketReceived packet;
|
|
// Manually convert to CopyOnWriteBuffer to be sure capacity == size
|
|
// and asan bot can catch read buffer overflow.
|
|
EXPECT_TRUE(packet.Parse(rtc::CopyOnWriteBuffer(data)));
|
|
rtp_video_stream_receiver_->StartReceive();
|
|
rtp_video_stream_receiver_->OnRtpPacket(packet);
|
|
// Expect asan doesn't find anything.
|
|
}
|
|
|
|
TEST_F(RtpVideoStreamReceiverTest, GenericKeyFrameBitstreamError) {
|
|
RTPHeader rtp_header;
|
|
RTPVideoHeader video_header;
|
|
const std::vector<uint8_t> data({1, 2, 3, 4});
|
|
rtp_header.sequenceNumber = 1;
|
|
video_header.is_first_packet_in_frame = true;
|
|
video_header.is_last_packet_in_frame = true;
|
|
video_header.codec = kVideoCodecGeneric;
|
|
video_header.frame_type = VideoFrameType::kVideoFrameKey;
|
|
constexpr uint8_t expected_bitsteam[] = {1, 2, 3, 0xff};
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(
|
|
expected_bitsteam, sizeof(expected_bitsteam));
|
|
EXPECT_CALL(mock_on_complete_frame_callback_,
|
|
DoOnCompleteFrameFailBitstream(_));
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
data.data(), data.size(), rtp_header, video_header, absl::nullopt, false);
|
|
}
|
|
|
|
class RtpVideoStreamReceiverTestH264
|
|
: public RtpVideoStreamReceiverTest,
|
|
public ::testing::WithParamInterface<std::string> {
|
|
protected:
|
|
RtpVideoStreamReceiverTestH264() : RtpVideoStreamReceiverTest(GetParam()) {}
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(SpsPpsIdrIsKeyframe,
|
|
RtpVideoStreamReceiverTestH264,
|
|
Values("", "WebRTC-SpsPpsIdrIsH264Keyframe/Enabled/"));
|
|
|
|
TEST_P(RtpVideoStreamReceiverTestH264, InBandSpsPps) {
|
|
std::vector<uint8_t> sps_data;
|
|
RTPHeader rtp_header;
|
|
RTPVideoHeader sps_video_header = GetDefaultH264VideoHeader();
|
|
AddSps(&sps_video_header, 0, &sps_data);
|
|
rtp_header.sequenceNumber = 0;
|
|
sps_video_header.is_first_packet_in_frame = true;
|
|
sps_video_header.frame_type = VideoFrameType::kEmptyFrame;
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(
|
|
kH264StartCode, sizeof(kH264StartCode));
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(sps_data.data(),
|
|
sps_data.size());
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
sps_data.data(), sps_data.size(), rtp_header, sps_video_header,
|
|
absl::nullopt, false);
|
|
|
|
std::vector<uint8_t> pps_data;
|
|
RTPVideoHeader pps_video_header = GetDefaultH264VideoHeader();
|
|
AddPps(&pps_video_header, 0, 1, &pps_data);
|
|
rtp_header.sequenceNumber = 1;
|
|
pps_video_header.is_first_packet_in_frame = true;
|
|
pps_video_header.frame_type = VideoFrameType::kEmptyFrame;
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(
|
|
kH264StartCode, sizeof(kH264StartCode));
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(pps_data.data(),
|
|
pps_data.size());
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
pps_data.data(), pps_data.size(), rtp_header, pps_video_header,
|
|
absl::nullopt, false);
|
|
|
|
std::vector<uint8_t> idr_data;
|
|
RTPVideoHeader idr_video_header = GetDefaultH264VideoHeader();
|
|
AddIdr(&idr_video_header, 1);
|
|
rtp_header.sequenceNumber = 2;
|
|
idr_video_header.is_first_packet_in_frame = true;
|
|
idr_video_header.is_last_packet_in_frame = true;
|
|
idr_video_header.frame_type = VideoFrameType::kVideoFrameKey;
|
|
idr_data.insert(idr_data.end(), {0x65, 1, 2, 3});
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(
|
|
kH264StartCode, sizeof(kH264StartCode));
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(idr_data.data(),
|
|
idr_data.size());
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
idr_data.data(), idr_data.size(), rtp_header, idr_video_header,
|
|
absl::nullopt, false);
|
|
}
|
|
|
|
TEST_P(RtpVideoStreamReceiverTestH264, OutOfBandFmtpSpsPps) {
|
|
constexpr int kPayloadType = 99;
|
|
VideoCodec codec;
|
|
codec.plType = kPayloadType;
|
|
std::map<std::string, std::string> codec_params;
|
|
// Example parameter sets from https://tools.ietf.org/html/rfc3984#section-8.2
|
|
// .
|
|
codec_params.insert(
|
|
{cricket::kH264FmtpSpropParameterSets, "Z0IACpZTBYmI,aMljiA=="});
|
|
rtp_video_stream_receiver_->AddReceiveCodec(codec, codec_params,
|
|
/*raw_payload=*/false);
|
|
const uint8_t binary_sps[] = {0x67, 0x42, 0x00, 0x0a, 0x96,
|
|
0x53, 0x05, 0x89, 0x88};
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(
|
|
kH264StartCode, sizeof(kH264StartCode));
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(binary_sps,
|
|
sizeof(binary_sps));
|
|
const uint8_t binary_pps[] = {0x68, 0xc9, 0x63, 0x88};
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(
|
|
kH264StartCode, sizeof(kH264StartCode));
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(binary_pps,
|
|
sizeof(binary_pps));
|
|
|
|
std::vector<uint8_t> data;
|
|
RTPHeader rtp_header;
|
|
RTPVideoHeader video_header = GetDefaultH264VideoHeader();
|
|
AddIdr(&video_header, 0);
|
|
rtp_header.payloadType = kPayloadType;
|
|
rtp_header.sequenceNumber = 2;
|
|
video_header.is_first_packet_in_frame = true;
|
|
video_header.is_last_packet_in_frame = true;
|
|
video_header.codec = kVideoCodecH264;
|
|
video_header.frame_type = VideoFrameType::kVideoFrameKey;
|
|
data.insert(data.end(), {1, 2, 3});
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(
|
|
kH264StartCode, sizeof(kH264StartCode));
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data(),
|
|
data.size());
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
data.data(), data.size(), rtp_header, video_header, absl::nullopt, false);
|
|
}
|
|
|
|
TEST_F(RtpVideoStreamReceiverTest, PaddingInMediaStream) {
|
|
RTPHeader rtp_header;
|
|
RTPVideoHeader video_header = GetDefaultH264VideoHeader();
|
|
std::vector<uint8_t> data;
|
|
data.insert(data.end(), {1, 2, 3});
|
|
rtp_header.payloadType = 99;
|
|
rtp_header.sequenceNumber = 2;
|
|
video_header.is_first_packet_in_frame = true;
|
|
video_header.is_last_packet_in_frame = true;
|
|
video_header.codec = kVideoCodecGeneric;
|
|
video_header.frame_type = VideoFrameType::kVideoFrameKey;
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data(),
|
|
data.size());
|
|
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
data.data(), data.size(), rtp_header, video_header, absl::nullopt, false);
|
|
|
|
rtp_header.sequenceNumber = 3;
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
nullptr, 0, rtp_header, video_header, absl::nullopt, false);
|
|
|
|
rtp_header.sequenceNumber = 4;
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
|
|
video_header.frame_type = VideoFrameType::kVideoFrameDelta;
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
data.data(), data.size(), rtp_header, video_header, absl::nullopt, false);
|
|
|
|
rtp_header.sequenceNumber = 6;
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
data.data(), data.size(), rtp_header, video_header, absl::nullopt, false);
|
|
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
|
|
rtp_header.sequenceNumber = 5;
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
nullptr, 0, rtp_header, video_header, absl::nullopt, false);
|
|
}
|
|
|
|
TEST_F(RtpVideoStreamReceiverTest, RequestKeyframeIfFirstFrameIsDelta) {
|
|
RTPHeader rtp_header;
|
|
RTPVideoHeader video_header;
|
|
const std::vector<uint8_t> data({1, 2, 3, 4});
|
|
rtp_header.sequenceNumber = 1;
|
|
video_header.is_first_packet_in_frame = true;
|
|
video_header.is_last_packet_in_frame = true;
|
|
video_header.codec = kVideoCodecGeneric;
|
|
video_header.frame_type = VideoFrameType::kVideoFrameDelta;
|
|
EXPECT_CALL(mock_key_frame_request_sender_, RequestKeyFrame());
|
|
rtp_video_stream_receiver_->OnReceivedPayloadData(
|
|
data.data(), data.size(), rtp_header, video_header, absl::nullopt, false);
|
|
}
|
|
|
|
TEST_F(RtpVideoStreamReceiverTest, SecondarySinksGetRtpNotifications) {
|
|
rtp_video_stream_receiver_->StartReceive();
|
|
|
|
MockRtpPacketSink secondary_sink_1;
|
|
MockRtpPacketSink secondary_sink_2;
|
|
|
|
rtp_video_stream_receiver_->AddSecondarySink(&secondary_sink_1);
|
|
rtp_video_stream_receiver_->AddSecondarySink(&secondary_sink_2);
|
|
|
|
auto rtp_packet = CreateRtpPacketReceived();
|
|
EXPECT_CALL(secondary_sink_1, OnRtpPacket(SamePacketAs(*rtp_packet)));
|
|
EXPECT_CALL(secondary_sink_2, OnRtpPacket(SamePacketAs(*rtp_packet)));
|
|
|
|
rtp_video_stream_receiver_->OnRtpPacket(*rtp_packet);
|
|
|
|
// Test tear-down.
|
|
rtp_video_stream_receiver_->StopReceive();
|
|
rtp_video_stream_receiver_->RemoveSecondarySink(&secondary_sink_1);
|
|
rtp_video_stream_receiver_->RemoveSecondarySink(&secondary_sink_2);
|
|
}
|
|
|
|
TEST_F(RtpVideoStreamReceiverTest, RemovedSecondarySinksGetNoRtpNotifications) {
|
|
rtp_video_stream_receiver_->StartReceive();
|
|
|
|
MockRtpPacketSink secondary_sink;
|
|
|
|
rtp_video_stream_receiver_->AddSecondarySink(&secondary_sink);
|
|
rtp_video_stream_receiver_->RemoveSecondarySink(&secondary_sink);
|
|
|
|
auto rtp_packet = CreateRtpPacketReceived();
|
|
|
|
EXPECT_CALL(secondary_sink, OnRtpPacket(_)).Times(0);
|
|
|
|
rtp_video_stream_receiver_->OnRtpPacket(*rtp_packet);
|
|
|
|
// Test tear-down.
|
|
rtp_video_stream_receiver_->StopReceive();
|
|
}
|
|
|
|
TEST_F(RtpVideoStreamReceiverTest,
|
|
OnlyRemovedSecondarySinksExcludedFromNotifications) {
|
|
rtp_video_stream_receiver_->StartReceive();
|
|
|
|
MockRtpPacketSink kept_secondary_sink;
|
|
MockRtpPacketSink removed_secondary_sink;
|
|
|
|
rtp_video_stream_receiver_->AddSecondarySink(&kept_secondary_sink);
|
|
rtp_video_stream_receiver_->AddSecondarySink(&removed_secondary_sink);
|
|
rtp_video_stream_receiver_->RemoveSecondarySink(&removed_secondary_sink);
|
|
|
|
auto rtp_packet = CreateRtpPacketReceived();
|
|
EXPECT_CALL(kept_secondary_sink, OnRtpPacket(SamePacketAs(*rtp_packet)));
|
|
|
|
rtp_video_stream_receiver_->OnRtpPacket(*rtp_packet);
|
|
|
|
// Test tear-down.
|
|
rtp_video_stream_receiver_->StopReceive();
|
|
rtp_video_stream_receiver_->RemoveSecondarySink(&kept_secondary_sink);
|
|
}
|
|
|
|
TEST_F(RtpVideoStreamReceiverTest,
|
|
SecondariesOfNonStartedStreamGetNoNotifications) {
|
|
// Explicitly showing that the stream is not in the |started| state,
|
|
// regardless of whether streams start out |started| or |stopped|.
|
|
rtp_video_stream_receiver_->StopReceive();
|
|
|
|
MockRtpPacketSink secondary_sink;
|
|
rtp_video_stream_receiver_->AddSecondarySink(&secondary_sink);
|
|
|
|
auto rtp_packet = CreateRtpPacketReceived();
|
|
EXPECT_CALL(secondary_sink, OnRtpPacket(_)).Times(0);
|
|
|
|
rtp_video_stream_receiver_->OnRtpPacket(*rtp_packet);
|
|
|
|
// Test tear-down.
|
|
rtp_video_stream_receiver_->RemoveSecondarySink(&secondary_sink);
|
|
}
|
|
|
|
class RtpVideoStreamReceiverGenericDescriptorTest
|
|
: public RtpVideoStreamReceiverTest,
|
|
public ::testing::WithParamInterface<int> {
|
|
public:
|
|
void RegisterRtpGenericFrameDescriptorExtension(
|
|
RtpHeaderExtensionMap* extension_map,
|
|
int version) {
|
|
constexpr int kId00 = 5;
|
|
constexpr int kId01 = 6;
|
|
switch (version) {
|
|
case 0:
|
|
extension_map->Register<RtpGenericFrameDescriptorExtension00>(kId00);
|
|
return;
|
|
case 1:
|
|
extension_map->Register<RtpGenericFrameDescriptorExtension01>(kId01);
|
|
return;
|
|
}
|
|
RTC_NOTREACHED();
|
|
}
|
|
|
|
bool SetExtensionRtpGenericFrameDescriptorExtension(
|
|
const RtpGenericFrameDescriptor& generic_descriptor,
|
|
RtpPacketReceived* rtp_packet,
|
|
int version) {
|
|
switch (version) {
|
|
case 0:
|
|
return rtp_packet->SetExtension<RtpGenericFrameDescriptorExtension00>(
|
|
generic_descriptor);
|
|
case 1:
|
|
return rtp_packet->SetExtension<RtpGenericFrameDescriptorExtension01>(
|
|
generic_descriptor);
|
|
}
|
|
RTC_NOTREACHED();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(,
|
|
RtpVideoStreamReceiverGenericDescriptorTest,
|
|
Values(0, 1));
|
|
|
|
TEST_P(RtpVideoStreamReceiverGenericDescriptorTest,
|
|
ParseGenericDescriptorOnePacket) {
|
|
const int version = GetParam();
|
|
|
|
const std::vector<uint8_t> data = {0, 1, 2, 3, 4};
|
|
const int kPayloadType = 123;
|
|
const int kSpatialIndex = 1;
|
|
|
|
VideoCodec codec;
|
|
codec.plType = kPayloadType;
|
|
rtp_video_stream_receiver_->AddReceiveCodec(codec, {}, /*raw_payload=*/false);
|
|
rtp_video_stream_receiver_->StartReceive();
|
|
|
|
RtpHeaderExtensionMap extension_map;
|
|
RegisterRtpGenericFrameDescriptorExtension(&extension_map, version);
|
|
RtpPacketReceived rtp_packet(&extension_map);
|
|
|
|
RtpGenericFrameDescriptor generic_descriptor;
|
|
generic_descriptor.SetFirstPacketInSubFrame(true);
|
|
generic_descriptor.SetLastPacketInSubFrame(true);
|
|
generic_descriptor.SetFrameId(100);
|
|
generic_descriptor.SetSpatialLayersBitmask(1 << kSpatialIndex);
|
|
generic_descriptor.AddFrameDependencyDiff(90);
|
|
generic_descriptor.AddFrameDependencyDiff(80);
|
|
ASSERT_TRUE(SetExtensionRtpGenericFrameDescriptorExtension(
|
|
generic_descriptor, &rtp_packet, version));
|
|
|
|
uint8_t* payload = rtp_packet.SetPayloadSize(data.size());
|
|
memcpy(payload, data.data(), data.size());
|
|
// The first byte is the header, so we ignore the first byte of |data|.
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data() + 1,
|
|
data.size() - 1);
|
|
|
|
rtp_packet.SetMarker(true);
|
|
rtp_packet.SetPayloadType(kPayloadType);
|
|
rtp_packet.SetSequenceNumber(1);
|
|
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame)
|
|
.WillOnce(Invoke([kSpatialIndex](video_coding::EncodedFrame* frame) {
|
|
EXPECT_EQ(frame->num_references, 2U);
|
|
EXPECT_EQ(frame->references[0], frame->id.picture_id - 90);
|
|
EXPECT_EQ(frame->references[1], frame->id.picture_id - 80);
|
|
EXPECT_EQ(frame->id.spatial_layer, kSpatialIndex);
|
|
}));
|
|
|
|
rtp_video_stream_receiver_->OnRtpPacket(rtp_packet);
|
|
}
|
|
|
|
TEST_P(RtpVideoStreamReceiverGenericDescriptorTest,
|
|
ParseGenericDescriptorTwoPackets) {
|
|
const int version = GetParam();
|
|
|
|
const std::vector<uint8_t> data = {0, 1, 2, 3, 4};
|
|
const int kPayloadType = 123;
|
|
const int kSpatialIndex = 1;
|
|
|
|
VideoCodec codec;
|
|
codec.plType = kPayloadType;
|
|
rtp_video_stream_receiver_->AddReceiveCodec(codec, {}, /*raw_payload=*/false);
|
|
rtp_video_stream_receiver_->StartReceive();
|
|
|
|
RtpHeaderExtensionMap extension_map;
|
|
RegisterRtpGenericFrameDescriptorExtension(&extension_map, version);
|
|
RtpPacketReceived first_packet(&extension_map);
|
|
|
|
RtpGenericFrameDescriptor first_packet_descriptor;
|
|
first_packet_descriptor.SetFirstPacketInSubFrame(true);
|
|
first_packet_descriptor.SetLastPacketInSubFrame(false);
|
|
first_packet_descriptor.SetFrameId(100);
|
|
first_packet_descriptor.SetSpatialLayersBitmask(1 << kSpatialIndex);
|
|
first_packet_descriptor.SetResolution(480, 360);
|
|
ASSERT_TRUE(SetExtensionRtpGenericFrameDescriptorExtension(
|
|
first_packet_descriptor, &first_packet, version));
|
|
|
|
uint8_t* first_packet_payload = first_packet.SetPayloadSize(data.size());
|
|
memcpy(first_packet_payload, data.data(), data.size());
|
|
// The first byte is the header, so we ignore the first byte of |data|.
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data() + 1,
|
|
data.size() - 1);
|
|
|
|
first_packet.SetPayloadType(kPayloadType);
|
|
first_packet.SetSequenceNumber(1);
|
|
rtp_video_stream_receiver_->OnRtpPacket(first_packet);
|
|
|
|
RtpPacketReceived second_packet(&extension_map);
|
|
RtpGenericFrameDescriptor second_packet_descriptor;
|
|
second_packet_descriptor.SetFirstPacketInSubFrame(false);
|
|
second_packet_descriptor.SetLastPacketInSubFrame(true);
|
|
ASSERT_TRUE(SetExtensionRtpGenericFrameDescriptorExtension(
|
|
second_packet_descriptor, &second_packet, version));
|
|
|
|
second_packet.SetMarker(true);
|
|
second_packet.SetPayloadType(kPayloadType);
|
|
second_packet.SetSequenceNumber(2);
|
|
|
|
uint8_t* second_packet_payload = second_packet.SetPayloadSize(data.size());
|
|
memcpy(second_packet_payload, data.data(), data.size());
|
|
// The first byte is the header, so we ignore the first byte of |data|.
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data() + 1,
|
|
data.size() - 1);
|
|
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame)
|
|
.WillOnce(Invoke([kSpatialIndex](video_coding::EncodedFrame* frame) {
|
|
EXPECT_EQ(frame->num_references, 0U);
|
|
EXPECT_EQ(frame->id.spatial_layer, kSpatialIndex);
|
|
EXPECT_EQ(frame->EncodedImage()._encodedWidth, 480u);
|
|
EXPECT_EQ(frame->EncodedImage()._encodedHeight, 360u);
|
|
}));
|
|
|
|
rtp_video_stream_receiver_->OnRtpPacket(second_packet);
|
|
}
|
|
|
|
TEST_F(RtpVideoStreamReceiverGenericDescriptorTest,
|
|
DropPacketsWithMultipleVersionsOfExtension) {
|
|
const std::vector<uint8_t> data = {0, 1, 2, 3, 4};
|
|
const int kPayloadType = 123;
|
|
|
|
VideoCodec codec;
|
|
codec.plType = kPayloadType;
|
|
rtp_video_stream_receiver_->AddReceiveCodec(codec, {}, /*raw_payload=*/false);
|
|
rtp_video_stream_receiver_->StartReceive();
|
|
|
|
RtpHeaderExtensionMap extension_map;
|
|
RegisterRtpGenericFrameDescriptorExtension(&extension_map, 0);
|
|
RegisterRtpGenericFrameDescriptorExtension(&extension_map, 1);
|
|
RtpPacketReceived rtp_packet(&extension_map);
|
|
|
|
RtpGenericFrameDescriptor generic_descriptors[2];
|
|
for (size_t i = 0; i < 2; ++i) {
|
|
generic_descriptors[i].SetFirstPacketInSubFrame(true);
|
|
generic_descriptors[i].SetLastPacketInSubFrame(true);
|
|
generic_descriptors[i].SetFrameId(100);
|
|
ASSERT_TRUE(SetExtensionRtpGenericFrameDescriptorExtension(
|
|
generic_descriptors[i], &rtp_packet, i));
|
|
}
|
|
|
|
uint8_t* payload = rtp_packet.SetPayloadSize(data.size());
|
|
memcpy(payload, data.data(), data.size());
|
|
// The first byte is the header, so we ignore the first byte of |data|.
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data() + 1,
|
|
data.size() - 1);
|
|
|
|
rtp_packet.SetMarker(true);
|
|
rtp_packet.SetPayloadType(kPayloadType);
|
|
rtp_packet.SetSequenceNumber(1);
|
|
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame).Times(0);
|
|
|
|
rtp_video_stream_receiver_->OnRtpPacket(rtp_packet);
|
|
}
|
|
|
|
TEST_P(RtpVideoStreamReceiverGenericDescriptorTest,
|
|
ParseGenericDescriptorRawPayload) {
|
|
const int version = GetParam();
|
|
|
|
const std::vector<uint8_t> data = {0, 1, 2, 3, 4};
|
|
const int kPayloadType = 123;
|
|
|
|
VideoCodec codec;
|
|
codec.plType = kPayloadType;
|
|
rtp_video_stream_receiver_->AddReceiveCodec(codec, {}, /*raw_payload=*/true);
|
|
rtp_video_stream_receiver_->StartReceive();
|
|
|
|
RtpHeaderExtensionMap extension_map;
|
|
RegisterRtpGenericFrameDescriptorExtension(&extension_map, version);
|
|
RtpPacketReceived rtp_packet(&extension_map);
|
|
|
|
RtpGenericFrameDescriptor generic_descriptor;
|
|
generic_descriptor.SetFirstPacketInSubFrame(true);
|
|
generic_descriptor.SetLastPacketInSubFrame(true);
|
|
ASSERT_TRUE(SetExtensionRtpGenericFrameDescriptorExtension(
|
|
generic_descriptor, &rtp_packet, version));
|
|
|
|
uint8_t* payload = rtp_packet.SetPayloadSize(data.size());
|
|
memcpy(payload, data.data(), data.size());
|
|
mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data(),
|
|
data.size());
|
|
|
|
rtp_packet.SetMarker(true);
|
|
rtp_packet.SetPayloadType(kPayloadType);
|
|
rtp_packet.SetSequenceNumber(1);
|
|
|
|
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame);
|
|
rtp_video_stream_receiver_->OnRtpPacket(rtp_packet);
|
|
}
|
|
|
|
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
|
TEST_F(RtpVideoStreamReceiverTest, RepeatedSecondarySinkDisallowed) {
|
|
MockRtpPacketSink secondary_sink;
|
|
|
|
rtp_video_stream_receiver_->AddSecondarySink(&secondary_sink);
|
|
EXPECT_DEATH(rtp_video_stream_receiver_->AddSecondarySink(&secondary_sink),
|
|
"");
|
|
|
|
// Test tear-down.
|
|
rtp_video_stream_receiver_->RemoveSecondarySink(&secondary_sink);
|
|
}
|
|
#endif
|
|
|
|
} // namespace webrtc
|