/* * Copyright (c) 2013 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 "modules/audio_coding/acm2/acm_receiver.h" #include // std::min #include #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "modules/audio_coding/acm2/rent_a_codec.h" #include "modules/audio_coding/include/audio_coding_module.h" #include "modules/audio_coding/neteq/tools/rtp_generator.h" #include "rtc_base/checks.h" #include "rtc_base/numerics/safe_conversions.h" #include "system_wrappers/include/clock.h" #include "test/gtest.h" #include "test/testsupport/fileutils.h" namespace webrtc { namespace acm2 { namespace { bool CodecsEqual(const CodecInst& codec_a, const CodecInst& codec_b) { if (strcmp(codec_a.plname, codec_b.plname) != 0 || codec_a.plfreq != codec_b.plfreq || codec_a.pltype != codec_b.pltype || codec_b.channels != codec_a.channels) return false; return true; } struct CodecIdInst { explicit CodecIdInst(RentACodec::CodecId codec_id) { const auto codec_ix = RentACodec::CodecIndexFromId(codec_id); EXPECT_TRUE(codec_ix); id = *codec_ix; const auto codec_inst = RentACodec::CodecInstById(codec_id); EXPECT_TRUE(codec_inst); inst = *codec_inst; } int id; CodecInst inst; }; } // namespace class AcmReceiverTestOldApi : public AudioPacketizationCallback, public ::testing::Test { protected: AcmReceiverTestOldApi() : timestamp_(0), packet_sent_(false), last_packet_send_timestamp_(timestamp_), last_frame_type_(kEmptyFrame) { config_.decoder_factory = CreateBuiltinAudioDecoderFactory(); } ~AcmReceiverTestOldApi() {} void SetUp() override { acm_.reset(AudioCodingModule::Create(config_)); receiver_.reset(new AcmReceiver(config_)); ASSERT_TRUE(receiver_.get() != NULL); ASSERT_TRUE(acm_.get() != NULL); codecs_ = RentACodec::Database(); acm_->InitializeReceiver(); acm_->RegisterTransportCallback(this); rtp_header_.header.sequenceNumber = 0; rtp_header_.header.timestamp = 0; rtp_header_.header.markerBit = false; rtp_header_.header.ssrc = 0x12345678; // Arbitrary. rtp_header_.header.numCSRCs = 0; rtp_header_.header.payloadType = 0; rtp_header_.frameType = kAudioFrameSpeech; rtp_header_.type.Audio.isCNG = false; } void TearDown() override {} void InsertOnePacketOfSilence(int codec_id) { CodecInst codec = *RentACodec::CodecInstById(*RentACodec::CodecIdFromIndex(codec_id)); if (timestamp_ == 0) { // This is the first time inserting audio. ASSERT_EQ(0, acm_->RegisterSendCodec(codec)); } else { auto current_codec = acm_->SendCodec(); ASSERT_TRUE(current_codec); if (!CodecsEqual(codec, *current_codec)) ASSERT_EQ(0, acm_->RegisterSendCodec(codec)); } AudioFrame frame; // Frame setup according to the codec. frame.sample_rate_hz_ = codec.plfreq; frame.samples_per_channel_ = codec.plfreq / 100; // 10 ms. frame.num_channels_ = codec.channels; frame.Mute(); packet_sent_ = false; last_packet_send_timestamp_ = timestamp_; while (!packet_sent_) { frame.timestamp_ = timestamp_; timestamp_ += rtc::checked_cast(frame.samples_per_channel_); ASSERT_GE(acm_->Add10MsData(frame), 0); } } template void AddSetOfCodecs(const RentACodec::CodecId(&ids)[N]) { for (auto id : ids) { const auto i = RentACodec::CodecIndexFromId(id); ASSERT_TRUE(i); ASSERT_EQ(0, receiver_->AddCodec(*i, codecs_[*i].pltype, codecs_[*i].channels, codecs_[*i].plfreq, nullptr, codecs_[*i].plname)); } } int SendData(FrameType frame_type, uint8_t payload_type, uint32_t timestamp, const uint8_t* payload_data, size_t payload_len_bytes, const RTPFragmentationHeader* fragmentation) override { if (frame_type == kEmptyFrame) return 0; rtp_header_.header.payloadType = payload_type; rtp_header_.frameType = frame_type; if (frame_type == kAudioFrameSpeech) rtp_header_.type.Audio.isCNG = false; else rtp_header_.type.Audio.isCNG = true; rtp_header_.header.timestamp = timestamp; int ret_val = receiver_->InsertPacket( rtp_header_, rtc::ArrayView(payload_data, payload_len_bytes)); if (ret_val < 0) { assert(false); return -1; } rtp_header_.header.sequenceNumber++; packet_sent_ = true; last_frame_type_ = frame_type; return 0; } AudioCodingModule::Config config_; std::unique_ptr receiver_; rtc::ArrayView codecs_; std::unique_ptr acm_; WebRtcRTPHeader rtp_header_; uint32_t timestamp_; bool packet_sent_; // Set when SendData is called reset when inserting audio. uint32_t last_packet_send_timestamp_; FrameType last_frame_type_; }; #if defined(WEBRTC_ANDROID) #define MAYBE_AddCodecGetCodec DISABLED_AddCodecGetCodec #else #define MAYBE_AddCodecGetCodec AddCodecGetCodec #endif TEST_F(AcmReceiverTestOldApi, MAYBE_AddCodecGetCodec) { // Add codec. for (size_t n = 0; n < codecs_.size(); ++n) { if (n & 0x1) { // Just add codecs with odd index. EXPECT_EQ( 0, receiver_->AddCodec(rtc::checked_cast(n), codecs_[n].pltype, codecs_[n].channels, codecs_[n].plfreq, NULL, codecs_[n].plname)); } } // Get codec and compare. for (size_t n = 0; n < codecs_.size(); ++n) { CodecInst my_codec; if (n & 0x1) { // Codecs with odd index should match the reference. EXPECT_EQ(0, receiver_->DecoderByPayloadType(codecs_[n].pltype, &my_codec)); EXPECT_TRUE(CodecsEqual(codecs_[n], my_codec)); } else { // Codecs with even index are not registered. EXPECT_EQ(-1, receiver_->DecoderByPayloadType(codecs_[n].pltype, &my_codec)); } } } #if defined(WEBRTC_ANDROID) #define MAYBE_AddCodecChangePayloadType DISABLED_AddCodecChangePayloadType #else #define MAYBE_AddCodecChangePayloadType AddCodecChangePayloadType #endif TEST_F(AcmReceiverTestOldApi, MAYBE_AddCodecChangePayloadType) { const CodecIdInst codec1(RentACodec::CodecId::kPCMA); CodecInst codec2 = codec1.inst; ++codec2.pltype; CodecInst test_codec; // Register the same codec with different payloads. EXPECT_EQ(0, receiver_->AddCodec(codec1.id, codec1.inst.pltype, codec1.inst.channels, codec1.inst.plfreq, nullptr, codec1.inst.plname)); EXPECT_EQ(0, receiver_->AddCodec(codec1.id, codec2.pltype, codec2.channels, codec2.plfreq, NULL, codec2.plname)); // Both payload types should exist. EXPECT_EQ(0, receiver_->DecoderByPayloadType(codec1.inst.pltype, &test_codec)); EXPECT_EQ(true, CodecsEqual(codec1.inst, test_codec)); EXPECT_EQ(0, receiver_->DecoderByPayloadType(codec2.pltype, &test_codec)); EXPECT_EQ(true, CodecsEqual(codec2, test_codec)); } #if defined(WEBRTC_ANDROID) #define MAYBE_AddCodecChangeCodecId DISABLED_AddCodecChangeCodecId #else #define MAYBE_AddCodecChangeCodecId AddCodecChangeCodecId #endif TEST_F(AcmReceiverTestOldApi, AddCodecChangeCodecId) { const CodecIdInst codec1(RentACodec::CodecId::kPCMU); CodecIdInst codec2(RentACodec::CodecId::kPCMA); codec2.inst.pltype = codec1.inst.pltype; CodecInst test_codec; // Register the same payload type with different codec ID. EXPECT_EQ(0, receiver_->AddCodec(codec1.id, codec1.inst.pltype, codec1.inst.channels, codec1.inst.plfreq, nullptr, codec1.inst.plname)); EXPECT_EQ(0, receiver_->AddCodec(codec2.id, codec2.inst.pltype, codec2.inst.channels, codec2.inst.plfreq, nullptr, codec2.inst.plname)); // Make sure that the last codec is used. EXPECT_EQ(0, receiver_->DecoderByPayloadType(codec2.inst.pltype, &test_codec)); EXPECT_EQ(true, CodecsEqual(codec2.inst, test_codec)); } #if defined(WEBRTC_ANDROID) #define MAYBE_AddCodecRemoveCodec DISABLED_AddCodecRemoveCodec #else #define MAYBE_AddCodecRemoveCodec AddCodecRemoveCodec #endif TEST_F(AcmReceiverTestOldApi, MAYBE_AddCodecRemoveCodec) { const CodecIdInst codec(RentACodec::CodecId::kPCMA); const int payload_type = codec.inst.pltype; EXPECT_EQ( 0, receiver_->AddCodec(codec.id, codec.inst.pltype, codec.inst.channels, codec.inst.plfreq, nullptr, codec.inst.plname)); // Remove non-existing codec should not fail. ACM1 legacy. EXPECT_EQ(0, receiver_->RemoveCodec(payload_type + 1)); // Remove an existing codec. EXPECT_EQ(0, receiver_->RemoveCodec(payload_type)); // Ask for the removed codec, must fail. CodecInst ci; EXPECT_EQ(-1, receiver_->DecoderByPayloadType(payload_type, &ci)); } #if defined(WEBRTC_ANDROID) #define MAYBE_SampleRate DISABLED_SampleRate #else #define MAYBE_SampleRate SampleRate #endif TEST_F(AcmReceiverTestOldApi, MAYBE_SampleRate) { const RentACodec::CodecId kCodecId[] = {RentACodec::CodecId::kISAC, RentACodec::CodecId::kISACSWB}; AddSetOfCodecs(kCodecId); AudioFrame frame; const int kOutSampleRateHz = 8000; // Different than codec sample rate. for (const auto codec_id : kCodecId) { const CodecIdInst codec(codec_id); const int num_10ms_frames = codec.inst.pacsize / (codec.inst.plfreq / 100); InsertOnePacketOfSilence(codec.id); for (int k = 0; k < num_10ms_frames; ++k) { bool muted; EXPECT_EQ(0, receiver_->GetAudio(kOutSampleRateHz, &frame, &muted)); } EXPECT_EQ(codec.inst.plfreq, receiver_->last_output_sample_rate_hz()); } } class AcmReceiverTestFaxModeOldApi : public AcmReceiverTestOldApi { protected: AcmReceiverTestFaxModeOldApi() { config_.neteq_config.playout_mode = kPlayoutFax; } void RunVerifyAudioFrame(RentACodec::CodecId codec_id) { // Make sure "fax mode" is enabled. This will avoid delay changes unless the // packet-loss concealment is made. We do this in order to make the // timestamp increments predictable; in normal mode, NetEq may decide to do // accelerate or pre-emptive expand operations after some time, offsetting // the timestamp. EXPECT_EQ(kPlayoutFax, config_.neteq_config.playout_mode); const RentACodec::CodecId kCodecId[] = {codec_id}; AddSetOfCodecs(kCodecId); const CodecIdInst codec(codec_id); const int output_sample_rate_hz = codec.inst.plfreq; const size_t output_channels = codec.inst.channels; const size_t samples_per_ms = rtc::checked_cast( rtc::CheckedDivExact(output_sample_rate_hz, 1000)); const int num_10ms_frames = rtc::CheckedDivExact( codec.inst.pacsize, rtc::checked_cast(10 * samples_per_ms)); const AudioFrame::VADActivity expected_vad_activity = output_sample_rate_hz > 16000 ? AudioFrame::kVadActive : AudioFrame::kVadPassive; // Expect the first output timestamp to be 5*fs/8000 samples before the // first inserted timestamp (because of NetEq's look-ahead). (This value is // defined in Expand::overlap_length_.) uint32_t expected_output_ts = last_packet_send_timestamp_ - rtc::CheckedDivExact(5 * output_sample_rate_hz, 8000); AudioFrame frame; bool muted; EXPECT_EQ(0, receiver_->GetAudio(output_sample_rate_hz, &frame, &muted)); // Expect timestamp = 0 before first packet is inserted. EXPECT_EQ(0u, frame.timestamp_); for (int i = 0; i < 5; ++i) { InsertOnePacketOfSilence(codec.id); for (int k = 0; k < num_10ms_frames; ++k) { EXPECT_EQ(0, receiver_->GetAudio(output_sample_rate_hz, &frame, &muted)); EXPECT_EQ(expected_output_ts, frame.timestamp_); expected_output_ts += rtc::checked_cast(10 * samples_per_ms); EXPECT_EQ(10 * samples_per_ms, frame.samples_per_channel_); EXPECT_EQ(output_sample_rate_hz, frame.sample_rate_hz_); EXPECT_EQ(output_channels, frame.num_channels_); EXPECT_EQ(AudioFrame::kNormalSpeech, frame.speech_type_); EXPECT_EQ(expected_vad_activity, frame.vad_activity_); EXPECT_FALSE(muted); } } } }; #if defined(WEBRTC_ANDROID) #define MAYBE_VerifyAudioFramePCMU DISABLED_VerifyAudioFramePCMU #else #define MAYBE_VerifyAudioFramePCMU VerifyAudioFramePCMU #endif TEST_F(AcmReceiverTestFaxModeOldApi, MAYBE_VerifyAudioFramePCMU) { RunVerifyAudioFrame(RentACodec::CodecId::kPCMU); } #if defined(WEBRTC_ANDROID) #define MAYBE_VerifyAudioFrameISAC DISABLED_VerifyAudioFrameISAC #else #define MAYBE_VerifyAudioFrameISAC VerifyAudioFrameISAC #endif TEST_F(AcmReceiverTestFaxModeOldApi, MAYBE_VerifyAudioFrameISAC) { RunVerifyAudioFrame(RentACodec::CodecId::kISAC); } #if defined(WEBRTC_ANDROID) #define MAYBE_VerifyAudioFrameOpus DISABLED_VerifyAudioFrameOpus #else #define MAYBE_VerifyAudioFrameOpus VerifyAudioFrameOpus #endif TEST_F(AcmReceiverTestFaxModeOldApi, MAYBE_VerifyAudioFrameOpus) { RunVerifyAudioFrame(RentACodec::CodecId::kOpus); } #if defined(WEBRTC_ANDROID) #define MAYBE_PostdecodingVad DISABLED_PostdecodingVad #else #define MAYBE_PostdecodingVad PostdecodingVad #endif TEST_F(AcmReceiverTestOldApi, MAYBE_PostdecodingVad) { EXPECT_TRUE(config_.neteq_config.enable_post_decode_vad); const CodecIdInst codec(RentACodec::CodecId::kPCM16Bwb); ASSERT_EQ( 0, receiver_->AddCodec(codec.id, codec.inst.pltype, codec.inst.channels, codec.inst.plfreq, nullptr, "")); const int kNumPackets = 5; const int num_10ms_frames = codec.inst.pacsize / (codec.inst.plfreq / 100); AudioFrame frame; for (int n = 0; n < kNumPackets; ++n) { InsertOnePacketOfSilence(codec.id); for (int k = 0; k < num_10ms_frames; ++k) { bool muted; ASSERT_EQ(0, receiver_->GetAudio(codec.inst.plfreq, &frame, &muted)); } } EXPECT_EQ(AudioFrame::kVadPassive, frame.vad_activity_); } class AcmReceiverTestPostDecodeVadPassiveOldApi : public AcmReceiverTestOldApi { protected: AcmReceiverTestPostDecodeVadPassiveOldApi() { config_.neteq_config.enable_post_decode_vad = false; } }; #if defined(WEBRTC_ANDROID) #define MAYBE_PostdecodingVad DISABLED_PostdecodingVad #else #define MAYBE_PostdecodingVad PostdecodingVad #endif TEST_F(AcmReceiverTestPostDecodeVadPassiveOldApi, MAYBE_PostdecodingVad) { EXPECT_FALSE(config_.neteq_config.enable_post_decode_vad); const CodecIdInst codec(RentACodec::CodecId::kPCM16Bwb); ASSERT_EQ( 0, receiver_->AddCodec(codec.id, codec.inst.pltype, codec.inst.channels, codec.inst.plfreq, nullptr, "")); const int kNumPackets = 5; const int num_10ms_frames = codec.inst.pacsize / (codec.inst.plfreq / 100); AudioFrame frame; for (int n = 0; n < kNumPackets; ++n) { InsertOnePacketOfSilence(codec.id); for (int k = 0; k < num_10ms_frames; ++k) { bool muted; ASSERT_EQ(0, receiver_->GetAudio(codec.inst.plfreq, &frame, &muted)); } } EXPECT_EQ(AudioFrame::kVadUnknown, frame.vad_activity_); } #if defined(WEBRTC_ANDROID) #define MAYBE_LastAudioCodec DISABLED_LastAudioCodec #else #define MAYBE_LastAudioCodec LastAudioCodec #endif #if defined(WEBRTC_CODEC_ISAC) TEST_F(AcmReceiverTestOldApi, MAYBE_LastAudioCodec) { const RentACodec::CodecId kCodecId[] = { RentACodec::CodecId::kISAC, RentACodec::CodecId::kPCMA, RentACodec::CodecId::kISACSWB, RentACodec::CodecId::kPCM16Bswb32kHz}; AddSetOfCodecs(kCodecId); const RentACodec::CodecId kCngId[] = { // Not including full-band. RentACodec::CodecId::kCNNB, RentACodec::CodecId::kCNWB, RentACodec::CodecId::kCNSWB}; AddSetOfCodecs(kCngId); // Register CNG at sender side. for (auto id : kCngId) ASSERT_EQ(0, acm_->RegisterSendCodec(CodecIdInst(id).inst)); CodecInst codec; // No audio payload is received. EXPECT_EQ(-1, receiver_->LastAudioCodec(&codec)); // Start with sending DTX. ASSERT_EQ(0, acm_->SetVAD(true, true, VADVeryAggr)); packet_sent_ = false; InsertOnePacketOfSilence(CodecIdInst(kCodecId[0]).id); // Enough to test // with one codec. ASSERT_TRUE(packet_sent_); EXPECT_EQ(kAudioFrameCN, last_frame_type_); // Has received, only, DTX. Last Audio codec is undefined. EXPECT_EQ(-1, receiver_->LastAudioCodec(&codec)); EXPECT_FALSE(receiver_->last_packet_sample_rate_hz()); for (auto id : kCodecId) { const CodecIdInst c(id); // Set DTX off to send audio payload. acm_->SetVAD(false, false, VADAggr); packet_sent_ = false; InsertOnePacketOfSilence(c.id); // Sanity check if Actually an audio payload received, and it should be // of type "speech." ASSERT_TRUE(packet_sent_); ASSERT_EQ(kAudioFrameSpeech, last_frame_type_); EXPECT_EQ(c.inst.plfreq, receiver_->last_packet_sample_rate_hz()); // Set VAD on to send DTX. Then check if the "Last Audio codec" returns // the expected codec. acm_->SetVAD(true, true, VADAggr); // Do as many encoding until a DTX is sent. while (last_frame_type_ != kAudioFrameCN) { packet_sent_ = false; InsertOnePacketOfSilence(c.id); ASSERT_TRUE(packet_sent_); } EXPECT_EQ(c.inst.plfreq, receiver_->last_packet_sample_rate_hz()); EXPECT_EQ(0, receiver_->LastAudioCodec(&codec)); EXPECT_TRUE(CodecsEqual(c.inst, codec)); } } #endif } // namespace acm2 } // namespace webrtc