From 95d12adf379afa9e5bb8d9fe0d89467c29aba0b2 Mon Sep 17 00:00:00 2001 From: Harald Alvestrand Date: Mon, 6 Feb 2023 12:22:44 +0000 Subject: [PATCH] Create unit test for the population of capture_start_ntp_time This verifies that receiving two RTCP SR packets is enough to get a defined capture start time stat. Bug: webrtc:13931 Change-Id: Ib5f7c2954eab6500917f25c44f523d3aedae5e94 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/291520 Reviewed-by: Danil Chapovalov Commit-Queue: Harald Alvestrand Cr-Commit-Position: refs/heads/main@{#39261} --- audio/BUILD.gn | 3 + audio/channel_receive_unittest.cc | 145 ++++++++++++++++++++++++++++-- 2 files changed, 143 insertions(+), 5 deletions(-) diff --git a/audio/BUILD.gn b/audio/BUILD.gn index a1445405c3..fc7e12ef38 100644 --- a/audio/BUILD.gn +++ b/audio/BUILD.gn @@ -226,14 +226,17 @@ if (rtc_include_tests) { sources = [ "channel_receive_unittest.cc" ] deps = [ ":audio", + "../api/audio_codecs:builtin_audio_decoder_factory", "../api/crypto:frame_decryptor_interface", "../api/task_queue:default_task_queue_factory", "../logging:mocks", "../modules/audio_device:audio_device_api", "../modules/audio_device:mock_audio_device", + "../modules/rtp_rtcp", "../modules/rtp_rtcp:rtp_rtcp_format", "../rtc_base:logging", "../rtc_base:threading", + "../test:audio_codec_mocks", "../test:mock_transport", "../test:test_support", "../test/time_controller", diff --git a/audio/channel_receive_unittest.cc b/audio/channel_receive_unittest.cc index b5654e2486..250ad4c51a 100644 --- a/audio/channel_receive_unittest.cc +++ b/audio/channel_receive_unittest.cc @@ -10,6 +10,8 @@ #include "audio/channel_receive.h" +#include "absl/strings/escaping.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/crypto/frame_decryptor_interface.h" #include "api/task_queue/default_task_queue_factory.h" #include "logging/rtc_event_log/mock/mock_rtc_event_log.h" @@ -17,10 +19,16 @@ #include "modules/audio_device/include/mock_audio_device.h" #include "modules/rtp_rtcp/source/byte_io.h" #include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" +#include "modules/rtp_rtcp/source/rtcp_packet/sdes.h" +#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/time_util.h" #include "rtc_base/logging.h" #include "rtc_base/thread.h" #include "test/gmock.h" #include "test/gtest.h" +#include "test/mock_audio_decoder_factory.h" #include "test/mock_transport.h" #include "test/time_controller/simulated_time_controller.h" @@ -30,38 +38,126 @@ namespace { using ::testing::NiceMock; using ::testing::NotNull; +using ::testing::Return; using ::testing::Test; constexpr uint32_t kLocalSsrc = 1111; constexpr uint32_t kRemoteSsrc = 2222; +// We run RTP data with 8 kHz PCMA (fixed payload type 8). +constexpr char kPayloadName[] = "PCMA"; +constexpr int kPayloadType = 8; +constexpr int kSampleRateHz = 8000; class ChannelReceiveTest : public Test { public: ChannelReceiveTest() : time_controller_(Timestamp::Seconds(5555)), - audio_device_module_(test::MockAudioDeviceModule::CreateStrict()) {} + audio_device_module_(test::MockAudioDeviceModule::CreateNice()), + audio_decoder_factory_(CreateBuiltinAudioDecoderFactory()) { + ON_CALL(*audio_device_module_, PlayoutDelay).WillByDefault(Return(0)); + } std::unique_ptr CreateTestChannelReceive() { CryptoOptions crypto_options; - return CreateChannelReceive( + auto channel = CreateChannelReceive( time_controller_.GetClock(), /* neteq_factory= */ nullptr, audio_device_module_.get(), &transport_, &event_log_, kLocalSsrc, kRemoteSsrc, /* jitter_buffer_max_packets= */ 0, /* jitter_buffer_fast_playout= */ false, /* jitter_buffer_min_delay_ms= */ 0, - /* enable_non_sender_rtt= */ false, - /* decoder_factory= */ nullptr, + /* enable_non_sender_rtt= */ false, audio_decoder_factory_, /* codec_pair_id= */ absl::nullopt, /* frame_decryptor_interface= */ nullptr, crypto_options, /* frame_transformer= */ nullptr); + channel->SetReceiveCodecs( + {{kPayloadType, {kPayloadName, kSampleRateHz, 1}}}); + return channel; } NtpTime NtpNow() { return time_controller_.GetClock()->CurrentNtpTime(); } + uint32_t RtpNow() { + // Note - the "random" offset of this timestamp is zero. + return rtc::TimeMillis() * 1000 / kSampleRateHz; + } + + RtpPacketReceived CreateRtpPacket() { + RtpPacketReceived packet; + packet.set_arrival_time(time_controller_.GetClock()->CurrentTime()); + packet.SetTimestamp(RtpNow()); + packet.SetSsrc(kLocalSsrc); + packet.SetPayloadType(kPayloadType); + // Packet size should be enough to give at least 10 ms of data. + // For PCMA, that's 80 bytes; this should be enough. + uint8_t* datapos = packet.SetPayloadSize(100); + memset(datapos, 0, 100); + return packet; + } + + std::vector CreateRtcpSenderReport() { + std::vector packet(1024); + size_t pos = 0; + rtcp::SenderReport report; + report.SetSenderSsrc(kRemoteSsrc); + report.SetNtp(NtpNow()); + report.SetRtpTimestamp(RtpNow()); + report.SetPacketCount(0); + report.SetOctetCount(0); + report.Create(&packet[0], &pos, packet.size(), nullptr); + // No report blocks. + packet.resize(pos); + return packet; + } + + std::vector CreateRtcpReceiverReport() { + rtcp::ReportBlock block; + block.SetMediaSsrc(kLocalSsrc); + // Middle 32 bits of the NTP timestamp from received SR + block.SetLastSr(CompactNtp(NtpNow())); + block.SetDelayLastSr(0); + + rtcp::ReceiverReport report; + report.SetSenderSsrc(kRemoteSsrc); + report.AddReportBlock(block); + + std::vector packet(1024); + size_t pos = 0; + report.Create(&packet[0], &pos, packet.size(), nullptr); + packet.resize(pos); + return packet; + } + + void HandleGeneratedRtcp(ChannelReceiveInterface& channel, + rtc::ArrayView packet) { + if (packet[1] == rtcp::ReceiverReport::kPacketType) { + // Ignore RR, it requires no response + } else { + RTC_LOG(LS_ERROR) << "Unexpected RTCP packet generated"; + RTC_LOG(LS_ERROR) << "Packet content " + << rtc::hex_encode_with_delimiter( + absl::string_view( + reinterpret_cast(packet.data()[0]), + packet.size()), + ' '); + } + } + + int64_t ProbeCaptureStartNtpTime(ChannelReceiveInterface& channel) { + // Computation of the capture_start_ntp_time_ms_ occurs when the + // audio data is pulled, not when it is received. So we need to + // inject an RTP packet, and then fetch its data. + AudioFrame audio_frame; + channel.OnRtpPacket(CreateRtpPacket()); + channel.GetAudioFrameWithInfo(kSampleRateHz, &audio_frame); + CallReceiveStatistics stats = channel.GetRTCPStatistics(); + return stats.capture_start_ntp_time_ms_; + } + protected: GlobalSimulatedTimeController time_controller_; rtc::scoped_refptr audio_device_module_; + rtc::scoped_refptr audio_decoder_factory_; MockTransport transport_; NiceMock event_log_; }; @@ -73,7 +169,6 @@ TEST_F(ChannelReceiveTest, CreateAndDestroy) { TEST_F(ChannelReceiveTest, ReceiveReportGeneratedOnTime) { auto channel = CreateTestChannelReceive(); - channel->SetReceiveCodecs({{10, {"L16", 44100, 1}}}); bool receiver_report_sent = false; EXPECT_CALL(transport_, SendRtcp) @@ -90,6 +185,46 @@ TEST_F(ChannelReceiveTest, ReceiveReportGeneratedOnTime) { EXPECT_TRUE(receiver_report_sent); } +TEST_F(ChannelReceiveTest, CaptureStartTimeBecomesValid) { + auto channel = CreateTestChannelReceive(); + + EXPECT_CALL(transport_, SendRtcp) + .WillRepeatedly([&](const uint8_t* packet, size_t length) { + HandleGeneratedRtcp(*channel, rtc::MakeArrayView(packet, length)); + return true; + }); + // Before any packets are sent, CaptureStartTime is invalid. + EXPECT_EQ(ProbeCaptureStartNtpTime(*channel), -1); + + // Must start playout, otherwise packet is discarded. + channel->StartPlayout(); + // Send one RTP packet. This causes registration of the SSRC. + channel->OnRtpPacket(CreateRtpPacket()); + EXPECT_EQ(ProbeCaptureStartNtpTime(*channel), -1); + + // Receive a sender report. + auto rtcp_packet_1 = CreateRtcpSenderReport(); + channel->ReceivedRTCPPacket(rtcp_packet_1.data(), rtcp_packet_1.size()); + EXPECT_EQ(ProbeCaptureStartNtpTime(*channel), -1); + + time_controller_.AdvanceTime(TimeDelta::Seconds(5)); + + // Receive a receiver report. This is necessary, which is odd. + // Presumably it is because the receiver needs to know the RTT + // before it can compute the capture start NTP time. + // The receiver report must happen before the second sender report. + auto rtcp_rr = CreateRtcpReceiverReport(); + channel->ReceivedRTCPPacket(rtcp_rr.data(), rtcp_rr.size()); + EXPECT_EQ(ProbeCaptureStartNtpTime(*channel), -1); + + // Receive another sender report after 5 seconds. + // This should be enough to establish the capture start NTP time. + auto rtcp_packet_2 = CreateRtcpSenderReport(); + channel->ReceivedRTCPPacket(rtcp_packet_2.data(), rtcp_packet_2.size()); + + EXPECT_NE(ProbeCaptureStartNtpTime(*channel), -1); +} + } // namespace } // namespace voe } // namespace webrtc