| /* |
| * Copyright (c) 2020 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 "audio/voip/audio_channel.h" |
| |
| #include "absl/functional/any_invocable.h" |
| #include "api/audio_codecs/builtin_audio_decoder_factory.h" |
| #include "api/audio_codecs/builtin_audio_encoder_factory.h" |
| #include "api/call/transport.h" |
| #include "api/environment/environment.h" |
| #include "api/environment/environment_factory.h" |
| #include "api/task_queue/task_queue_base.h" |
| #include "api/task_queue/task_queue_factory.h" |
| #include "audio/voip/test/mock_task_queue.h" |
| #include "modules/audio_mixer/audio_mixer_impl.h" |
| #include "modules/audio_mixer/sine_wave_generator.h" |
| #include "modules/rtp_rtcp/source/rtp_packet_received.h" |
| #include "rtc_base/logging.h" |
| #include "test/gmock.h" |
| #include "test/gtest.h" |
| #include "test/mock_transport.h" |
| |
| namespace webrtc { |
| namespace { |
| |
| using ::testing::Invoke; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::Unused; |
| using ::testing::WithArg; |
| |
| constexpr uint64_t kStartTime = 123456789; |
| constexpr uint32_t kLocalSsrc = 0xdeadc0de; |
| constexpr int16_t kAudioLevel = 3004; // used for sine wave level |
| constexpr int kPcmuPayload = 0; |
| |
| class AudioChannelTest : public ::testing::Test { |
| public: |
| const SdpAudioFormat kPcmuFormat = {"pcmu", 8000, 1}; |
| |
| AudioChannelTest() |
| : fake_clock_(kStartTime), |
| wave_generator_(1000.0, kAudioLevel), |
| env_(CreateEnvironment( |
| &fake_clock_, |
| std::make_unique<MockTaskQueueFactory>(&task_queue_))) { |
| audio_mixer_ = AudioMixerImpl::Create(); |
| encoder_factory_ = CreateBuiltinAudioEncoderFactory(); |
| decoder_factory_ = CreateBuiltinAudioDecoderFactory(); |
| |
| // By default, run the queued task immediately. |
| ON_CALL(task_queue_, PostTaskImpl) |
| .WillByDefault(WithArg<0>( |
| [](absl::AnyInvocable<void() &&> task) { std::move(task)(); })); |
| } |
| |
| void SetUp() override { audio_channel_ = CreateAudioChannel(kLocalSsrc); } |
| |
| void TearDown() override { audio_channel_ = nullptr; } |
| |
| rtc::scoped_refptr<AudioChannel> CreateAudioChannel(uint32_t ssrc) { |
| // Use same audio mixer here for simplicity sake as we are not checking |
| // audio activity of RTP in our testcases. If we need to do test on audio |
| // signal activity then we need to assign audio mixer for each channel. |
| // Also this uses the same transport object for different audio channel to |
| // simplify network routing logic. |
| rtc::scoped_refptr<AudioChannel> audio_channel = |
| rtc::make_ref_counted<AudioChannel>( |
| env_, &transport_, ssrc, audio_mixer_.get(), decoder_factory_); |
| audio_channel->SetEncoder( |
| kPcmuPayload, kPcmuFormat, |
| encoder_factory_->Create(env_, kPcmuFormat, |
| {.payload_type = kPcmuPayload})); |
| audio_channel->SetReceiveCodecs({{kPcmuPayload, kPcmuFormat}}); |
| audio_channel->StartSend(); |
| audio_channel->StartPlay(); |
| return audio_channel; |
| } |
| |
| std::unique_ptr<AudioFrame> GetAudioFrame(int order) { |
| auto frame = std::make_unique<AudioFrame>(); |
| frame->sample_rate_hz_ = kPcmuFormat.clockrate_hz; |
| frame->samples_per_channel_ = kPcmuFormat.clockrate_hz / 100; // 10 ms. |
| frame->num_channels_ = kPcmuFormat.num_channels; |
| frame->timestamp_ = frame->samples_per_channel_ * order; |
| wave_generator_.GenerateNextFrame(frame.get()); |
| return frame; |
| } |
| |
| SimulatedClock fake_clock_; |
| SineWaveGenerator wave_generator_; |
| NiceMock<MockTransport> transport_; |
| NiceMock<MockTaskQueue> task_queue_; |
| const Environment env_; |
| rtc::scoped_refptr<AudioMixer> audio_mixer_; |
| rtc::scoped_refptr<AudioDecoderFactory> decoder_factory_; |
| rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_; |
| rtc::scoped_refptr<AudioChannel> audio_channel_; |
| }; |
| |
| // Validate RTP packet generation by feeding audio frames with sine wave. |
| // Resulted RTP packet is looped back into AudioChannel and gets decoded into |
| // audio frame to see if it has some signal to indicate its validity. |
| TEST_F(AudioChannelTest, PlayRtpByLocalLoop) { |
| auto loop_rtp = [&](rtc::ArrayView<const uint8_t> packet, Unused) { |
| audio_channel_->ReceivedRTPPacket(packet); |
| return true; |
| }; |
| EXPECT_CALL(transport_, SendRtp).WillOnce(Invoke(loop_rtp)); |
| |
| auto audio_sender = audio_channel_->GetAudioSender(); |
| audio_sender->SendAudioData(GetAudioFrame(0)); |
| audio_sender->SendAudioData(GetAudioFrame(1)); |
| |
| AudioFrame empty_frame, audio_frame; |
| empty_frame.Mute(); |
| empty_frame.mutable_data(); // This will zero out the data. |
| audio_frame.CopyFrom(empty_frame); |
| audio_mixer_->Mix(/*number_of_channels*/ 1, &audio_frame); |
| |
| // We expect now audio frame to pick up something. |
| EXPECT_NE(memcmp(empty_frame.data(), audio_frame.data(), |
| AudioFrame::kMaxDataSizeBytes), |
| 0); |
| } |
| |
| // Validate assigned local SSRC is resulted in RTP packet. |
| TEST_F(AudioChannelTest, VerifyLocalSsrcAsAssigned) { |
| RtpPacketReceived rtp; |
| auto loop_rtp = [&](rtc::ArrayView<const uint8_t> packet, Unused) { |
| rtp.Parse(packet); |
| return true; |
| }; |
| EXPECT_CALL(transport_, SendRtp).WillOnce(Invoke(loop_rtp)); |
| |
| auto audio_sender = audio_channel_->GetAudioSender(); |
| audio_sender->SendAudioData(GetAudioFrame(0)); |
| audio_sender->SendAudioData(GetAudioFrame(1)); |
| |
| EXPECT_EQ(rtp.Ssrc(), kLocalSsrc); |
| } |
| |
| // Check metrics after processing an RTP packet. |
| TEST_F(AudioChannelTest, TestIngressStatistics) { |
| auto loop_rtp = [&](rtc::ArrayView<const uint8_t> packet, Unused) { |
| audio_channel_->ReceivedRTPPacket(packet); |
| return true; |
| }; |
| EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(loop_rtp)); |
| |
| auto audio_sender = audio_channel_->GetAudioSender(); |
| audio_sender->SendAudioData(GetAudioFrame(0)); |
| audio_sender->SendAudioData(GetAudioFrame(1)); |
| |
| AudioFrame audio_frame; |
| audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame); |
| audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame); |
| |
| std::optional<IngressStatistics> ingress_stats = |
| audio_channel_->GetIngressStatistics(); |
| EXPECT_TRUE(ingress_stats); |
| EXPECT_EQ(ingress_stats->neteq_stats.total_samples_received, 160ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.concealed_samples, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.concealment_events, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.inserted_samples_for_deceleration, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.removed_samples_for_acceleration, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.silent_concealed_samples, 0ULL); |
| // To extract the jitter buffer length in millisecond, jitter_buffer_delay_ms |
| // needs to be divided by jitter_buffer_emitted_count (number of samples). |
| EXPECT_EQ(ingress_stats->neteq_stats.jitter_buffer_delay_ms, 1600ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.jitter_buffer_emitted_count, 160ULL); |
| EXPECT_GT(ingress_stats->neteq_stats.jitter_buffer_target_delay_ms, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.interruption_count, 0); |
| EXPECT_EQ(ingress_stats->neteq_stats.total_interruption_duration_ms, 0); |
| EXPECT_DOUBLE_EQ(ingress_stats->total_duration, 0.02); |
| |
| // Now without any RTP pending in jitter buffer pull more. |
| audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame); |
| audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame); |
| |
| // Send another RTP packet to intentionally break PLC. |
| audio_sender->SendAudioData(GetAudioFrame(2)); |
| audio_sender->SendAudioData(GetAudioFrame(3)); |
| |
| ingress_stats = audio_channel_->GetIngressStatistics(); |
| EXPECT_TRUE(ingress_stats); |
| EXPECT_EQ(ingress_stats->neteq_stats.total_samples_received, 320ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.concealed_samples, 168ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.concealment_events, 1ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.inserted_samples_for_deceleration, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.removed_samples_for_acceleration, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.silent_concealed_samples, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.jitter_buffer_delay_ms, 1600ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.jitter_buffer_emitted_count, 160ULL); |
| EXPECT_GT(ingress_stats->neteq_stats.jitter_buffer_target_delay_ms, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.interruption_count, 0); |
| EXPECT_EQ(ingress_stats->neteq_stats.total_interruption_duration_ms, 0); |
| EXPECT_DOUBLE_EQ(ingress_stats->total_duration, 0.04); |
| |
| // Pull the last RTP packet. |
| audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame); |
| audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame); |
| |
| ingress_stats = audio_channel_->GetIngressStatistics(); |
| EXPECT_TRUE(ingress_stats); |
| EXPECT_EQ(ingress_stats->neteq_stats.total_samples_received, 480ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.concealed_samples, 168ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.concealment_events, 1ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.inserted_samples_for_deceleration, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.removed_samples_for_acceleration, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.silent_concealed_samples, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.jitter_buffer_delay_ms, 3200ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.jitter_buffer_emitted_count, 320ULL); |
| EXPECT_GT(ingress_stats->neteq_stats.jitter_buffer_target_delay_ms, 0ULL); |
| EXPECT_EQ(ingress_stats->neteq_stats.interruption_count, 0); |
| EXPECT_EQ(ingress_stats->neteq_stats.total_interruption_duration_ms, 0); |
| EXPECT_DOUBLE_EQ(ingress_stats->total_duration, 0.06); |
| } |
| |
| // Check ChannelStatistics metric after processing RTP and RTCP packets. |
| TEST_F(AudioChannelTest, TestChannelStatistics) { |
| auto loop_rtp = [&](rtc::ArrayView<const uint8_t> packet, Unused) { |
| audio_channel_->ReceivedRTPPacket(packet); |
| return true; |
| }; |
| auto loop_rtcp = [&](rtc::ArrayView<const uint8_t> packet) { |
| audio_channel_->ReceivedRTCPPacket(packet); |
| return true; |
| }; |
| EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(loop_rtp)); |
| EXPECT_CALL(transport_, SendRtcp).WillRepeatedly(Invoke(loop_rtcp)); |
| |
| // Simulate microphone giving audio frame (10 ms). This will trigger transport |
| // to send RTP as handled in loop_rtp above. |
| auto audio_sender = audio_channel_->GetAudioSender(); |
| audio_sender->SendAudioData(GetAudioFrame(0)); |
| audio_sender->SendAudioData(GetAudioFrame(1)); |
| |
| // Simulate speaker requesting audio frame (10 ms). This will trigger VoIP |
| // engine to fetch audio samples from RTP packets stored in jitter buffer. |
| AudioFrame audio_frame; |
| audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame); |
| audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame); |
| |
| // Force sending RTCP SR report in order to have remote_rtcp field available |
| // in channel statistics. This will trigger transport to send RTCP as handled |
| // in loop_rtcp above. |
| audio_channel_->SendRTCPReportForTesting(kRtcpSr); |
| |
| std::optional<ChannelStatistics> channel_stats = |
| audio_channel_->GetChannelStatistics(); |
| EXPECT_TRUE(channel_stats); |
| |
| EXPECT_EQ(channel_stats->packets_sent, 1ULL); |
| EXPECT_EQ(channel_stats->bytes_sent, 160ULL); |
| |
| EXPECT_EQ(channel_stats->packets_received, 1ULL); |
| EXPECT_EQ(channel_stats->bytes_received, 160ULL); |
| EXPECT_EQ(channel_stats->jitter, 0); |
| EXPECT_EQ(channel_stats->packets_lost, 0); |
| EXPECT_EQ(channel_stats->remote_ssrc.value(), kLocalSsrc); |
| |
| EXPECT_TRUE(channel_stats->remote_rtcp.has_value()); |
| |
| EXPECT_EQ(channel_stats->remote_rtcp->jitter, 0); |
| EXPECT_EQ(channel_stats->remote_rtcp->packets_lost, 0); |
| EXPECT_EQ(channel_stats->remote_rtcp->fraction_lost, 0); |
| EXPECT_GT(channel_stats->remote_rtcp->last_report_received_timestamp_ms, 0); |
| EXPECT_FALSE(channel_stats->remote_rtcp->round_trip_time.has_value()); |
| } |
| |
| // Check ChannelStatistics RTT metric after processing RTP and RTCP packets |
| // using three audio channels where each represents media endpoint. |
| // |
| // 1) AC1 <- RTP/RTCP -> AC2 |
| // 2) AC1 <- RTP/RTCP -> AC3 |
| // |
| // During step 1), AC1 should be able to check RTT from AC2's SSRC. |
| // During step 2), AC1 should be able to check RTT from AC3's SSRC. |
| TEST_F(AudioChannelTest, RttIsAvailableAfterChangeOfRemoteSsrc) { |
| // Create AC2 and AC3. |
| constexpr uint32_t kAc2Ssrc = 0xdeadbeef; |
| constexpr uint32_t kAc3Ssrc = 0xdeafbeef; |
| |
| auto ac_2 = CreateAudioChannel(kAc2Ssrc); |
| auto ac_3 = CreateAudioChannel(kAc3Ssrc); |
| |
| auto send_recv_rtp = [&](rtc::scoped_refptr<AudioChannel> rtp_sender, |
| rtc::scoped_refptr<AudioChannel> rtp_receiver) { |
| // Setup routing logic via transport_. |
| auto route_rtp = [&](rtc::ArrayView<const uint8_t> packet, Unused) { |
| rtp_receiver->ReceivedRTPPacket(packet); |
| return true; |
| }; |
| ON_CALL(transport_, SendRtp).WillByDefault(route_rtp); |
| |
| // This will trigger route_rtp callback via transport_. |
| rtp_sender->GetAudioSender()->SendAudioData(GetAudioFrame(0)); |
| rtp_sender->GetAudioSender()->SendAudioData(GetAudioFrame(1)); |
| |
| // Process received RTP in receiver. |
| AudioFrame audio_frame; |
| audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame); |
| audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame); |
| |
| // Revert to default to avoid using reference in route_rtp lambda. |
| ON_CALL(transport_, SendRtp).WillByDefault(Return(true)); |
| }; |
| |
| auto send_recv_rtcp = [&](rtc::scoped_refptr<AudioChannel> rtcp_sender, |
| rtc::scoped_refptr<AudioChannel> rtcp_receiver) { |
| // Setup routing logic via transport_. |
| auto route_rtcp = [&](rtc::ArrayView<const uint8_t> packet) { |
| rtcp_receiver->ReceivedRTCPPacket(packet); |
| return true; |
| }; |
| ON_CALL(transport_, SendRtcp).WillByDefault(route_rtcp); |
| |
| // This will trigger route_rtcp callback via transport_. |
| rtcp_sender->SendRTCPReportForTesting(kRtcpSr); |
| |
| // Revert to default to avoid using reference in route_rtcp lambda. |
| ON_CALL(transport_, SendRtcp).WillByDefault(Return(true)); |
| }; |
| |
| // AC1 <-- RTP/RTCP --> AC2 |
| send_recv_rtp(audio_channel_, ac_2); |
| send_recv_rtp(ac_2, audio_channel_); |
| send_recv_rtcp(audio_channel_, ac_2); |
| send_recv_rtcp(ac_2, audio_channel_); |
| |
| std::optional<ChannelStatistics> channel_stats = |
| audio_channel_->GetChannelStatistics(); |
| ASSERT_TRUE(channel_stats); |
| EXPECT_EQ(channel_stats->remote_ssrc, kAc2Ssrc); |
| ASSERT_TRUE(channel_stats->remote_rtcp); |
| EXPECT_GT(channel_stats->remote_rtcp->round_trip_time, 0.0); |
| |
| // AC1 <-- RTP/RTCP --> AC3 |
| send_recv_rtp(audio_channel_, ac_3); |
| send_recv_rtp(ac_3, audio_channel_); |
| send_recv_rtcp(audio_channel_, ac_3); |
| send_recv_rtcp(ac_3, audio_channel_); |
| |
| channel_stats = audio_channel_->GetChannelStatistics(); |
| ASSERT_TRUE(channel_stats); |
| EXPECT_EQ(channel_stats->remote_ssrc, kAc3Ssrc); |
| ASSERT_TRUE(channel_stats->remote_rtcp); |
| EXPECT_GT(channel_stats->remote_rtcp->round_trip_time, 0.0); |
| } |
| |
| } // namespace |
| } // namespace webrtc |