/* * Copyright (c) 2012 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/test/TestVADDTX.h" #include #include "absl/strings/match.h" #include "api/audio_codecs/audio_decoder_factory_template.h" #include "api/audio_codecs/audio_encoder_factory_template.h" #include "api/audio_codecs/ilbc/audio_decoder_ilbc.h" #include "api/audio_codecs/ilbc/audio_encoder_ilbc.h" #include "api/audio_codecs/isac/audio_decoder_isac_float.h" #include "api/audio_codecs/isac/audio_encoder_isac_float.h" #include "api/audio_codecs/opus/audio_decoder_opus.h" #include "api/audio_codecs/opus/audio_encoder_opus.h" #include "modules/audio_coding/codecs/cng/audio_encoder_cng.h" #include "modules/audio_coding/test/PCMFile.h" #include "rtc_base/strings/string_builder.h" #include "test/gtest.h" #include "test/testsupport/file_utils.h" namespace webrtc { MonitoringAudioPacketizationCallback::MonitoringAudioPacketizationCallback( AudioPacketizationCallback* next) : next_(next) { ResetStatistics(); } int32_t MonitoringAudioPacketizationCallback::SendData( AudioFrameType frame_type, uint8_t payload_type, uint32_t timestamp, const uint8_t* payload_data, size_t payload_len_bytes, int64_t absolute_capture_timestamp_ms) { counter_[static_cast(frame_type)]++; return next_->SendData(frame_type, payload_type, timestamp, payload_data, payload_len_bytes, absolute_capture_timestamp_ms); } void MonitoringAudioPacketizationCallback::PrintStatistics() { printf("\n"); printf("kEmptyFrame %u\n", counter_[static_cast(AudioFrameType::kEmptyFrame)]); printf("kAudioFrameSpeech %u\n", counter_[static_cast(AudioFrameType::kAudioFrameSpeech)]); printf("kAudioFrameCN %u\n", counter_[static_cast(AudioFrameType::kAudioFrameCN)]); printf("\n\n"); } void MonitoringAudioPacketizationCallback::ResetStatistics() { memset(counter_, 0, sizeof(counter_)); } void MonitoringAudioPacketizationCallback::GetStatistics(uint32_t* counter) { memcpy(counter, counter_, sizeof(counter_)); } TestVadDtx::TestVadDtx() : encoder_factory_(CreateAudioEncoderFactory()), decoder_factory_(CreateAudioDecoderFactory()), acm_send_(AudioCodingModule::Create( AudioCodingModule::Config(decoder_factory_))), acm_receive_(AudioCodingModule::Create( AudioCodingModule::Config(decoder_factory_))), channel_(std::make_unique()), packetization_callback_( std::make_unique( channel_.get())) { EXPECT_EQ( 0, acm_send_->RegisterTransportCallback(packetization_callback_.get())); channel_->RegisterReceiverACM(acm_receive_.get()); } bool TestVadDtx::RegisterCodec(const SdpAudioFormat& codec_format, absl::optional vad_mode) { constexpr int payload_type = 17, cn_payload_type = 117; bool added_comfort_noise = false; auto encoder = encoder_factory_->MakeAudioEncoder(payload_type, codec_format, absl::nullopt); if (vad_mode.has_value() && !absl::EqualsIgnoreCase(codec_format.name, "opus")) { AudioEncoderCngConfig config; config.speech_encoder = std::move(encoder); config.num_channels = 1; config.payload_type = cn_payload_type; config.vad_mode = vad_mode.value(); encoder = CreateComfortNoiseEncoder(std::move(config)); added_comfort_noise = true; } channel_->SetIsStereo(encoder->NumChannels() > 1); acm_send_->SetEncoder(std::move(encoder)); std::map receive_codecs = {{payload_type, codec_format}}; acm_receive_->SetReceiveCodecs(receive_codecs); return added_comfort_noise; } // Encoding a file and see if the numbers that various packets occur follow // the expectation. void TestVadDtx::Run(std::string in_filename, int frequency, int channels, std::string out_filename, bool append, const int* expects) { packetization_callback_->ResetStatistics(); PCMFile in_file; in_file.Open(in_filename, frequency, "rb"); in_file.ReadStereo(channels > 1); // Set test length to 1000 ms (100 blocks of 10 ms each). in_file.SetNum10MsBlocksToRead(100); // Fast-forward both files 500 ms (50 blocks). The first second of the file is // silence, but we want to keep half of that to test silence periods. in_file.FastForward(50); PCMFile out_file; if (append) { out_file.Open(out_filename, kOutputFreqHz, "ab"); } else { out_file.Open(out_filename, kOutputFreqHz, "wb"); } uint16_t frame_size_samples = in_file.PayloadLength10Ms(); AudioFrame audio_frame; while (!in_file.EndOfFile()) { in_file.Read10MsData(audio_frame); audio_frame.timestamp_ = time_stamp_; time_stamp_ += frame_size_samples; EXPECT_GE(acm_send_->Add10MsData(audio_frame), 0); bool muted; acm_receive_->PlayoutData10Ms(kOutputFreqHz, &audio_frame, &muted); ASSERT_FALSE(muted); out_file.Write10MsData(audio_frame); } in_file.Close(); out_file.Close(); #ifdef PRINT_STAT packetization_callback_->PrintStatistics(); #endif uint32_t stats[3]; packetization_callback_->GetStatistics(stats); packetization_callback_->ResetStatistics(); for (const auto& st : stats) { int i = &st - stats; // Calculate the current position in stats. switch (expects[i]) { case 0: { EXPECT_EQ(0u, st) << "stats[" << i << "] error. Output file " << out_filename; break; } case 1: { EXPECT_GT(st, 0u) << "stats[" << i << "] error. Output file " << out_filename; break; } } } } // Following is the implementation of TestWebRtcVadDtx. TestWebRtcVadDtx::TestWebRtcVadDtx() : output_file_num_(0) {} void TestWebRtcVadDtx::Perform() { RunTestCases({"ISAC", 16000, 1}); RunTestCases({"ISAC", 32000, 1}); RunTestCases({"ILBC", 8000, 1}); RunTestCases({"opus", 48000, 2}); } // Test various configurations on VAD/DTX. void TestWebRtcVadDtx::RunTestCases(const SdpAudioFormat& codec_format) { RegisterCodec(codec_format, absl::nullopt); Test(/*new_outfile=*/true, /*expect_vad_packets=*/codec_format.name == "opus"); RegisterCodec(codec_format, Vad::kVadAggressive); Test(/*new_outfile=*/false, /*expect_vad_packets=*/true); RegisterCodec(codec_format, Vad::kVadLowBitrate); Test(/*new_outfile=*/false, /*expect_vad_packets=*/true); RegisterCodec(codec_format, Vad::kVadVeryAggressive); Test(/*new_outfile=*/false, /*expect_vad_packets=*/true); RegisterCodec(codec_format, Vad::kVadNormal); Test(/*new_outfile=*/false, /*expect_vad_packets=*/true); } // Set the expectation and run the test. void TestWebRtcVadDtx::Test(bool new_outfile, bool expect_vad_packets) { int expects[] = {-1, 1, expect_vad_packets ? 1 : -1, 0, 0}; if (new_outfile) { output_file_num_++; } rtc::StringBuilder out_filename; out_filename << webrtc::test::OutputPath() << "testWebRtcVadDtx_outFile_" << output_file_num_ << ".pcm"; Run(webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"), 32000, 1, out_filename.str(), !new_outfile, expects); } // Following is the implementation of TestOpusDtx. void TestOpusDtx::Perform() { int expects[] = {0, 0, 0, 0, 0}; // Register Opus as send codec std::string out_filename = webrtc::test::OutputPath() + "testOpusDtx_outFile_mono.pcm"; RegisterCodec({"opus", 48000, 2}, absl::nullopt); acm_send_->ModifyEncoder([](std::unique_ptr* encoder_ptr) { (*encoder_ptr)->SetDtx(false); }); expects[static_cast(AudioFrameType::kEmptyFrame)] = 0; expects[static_cast(AudioFrameType::kAudioFrameSpeech)] = 1; expects[static_cast(AudioFrameType::kAudioFrameCN)] = 1; Run(webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"), 32000, 1, out_filename, false, expects); acm_send_->ModifyEncoder([](std::unique_ptr* encoder_ptr) { (*encoder_ptr)->SetDtx(true); }); expects[static_cast(AudioFrameType::kEmptyFrame)] = 1; expects[static_cast(AudioFrameType::kAudioFrameSpeech)] = 1; expects[static_cast(AudioFrameType::kAudioFrameCN)] = 1; Run(webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"), 32000, 1, out_filename, true, expects); // Register stereo Opus as send codec out_filename = webrtc::test::OutputPath() + "testOpusDtx_outFile_stereo.pcm"; RegisterCodec({"opus", 48000, 2, {{"stereo", "1"}}}, absl::nullopt); acm_send_->ModifyEncoder([](std::unique_ptr* encoder_ptr) { (*encoder_ptr)->SetDtx(false); }); expects[static_cast(AudioFrameType::kEmptyFrame)] = 0; expects[static_cast(AudioFrameType::kAudioFrameSpeech)] = 1; expects[static_cast(AudioFrameType::kAudioFrameCN)] = 0; Run(webrtc::test::ResourcePath("audio_coding/teststereo32kHz", "pcm"), 32000, 2, out_filename, false, expects); acm_send_->ModifyEncoder([](std::unique_ptr* encoder_ptr) { (*encoder_ptr)->SetDtx(true); // The default bitrate will not generate frames recognized as CN on desktop // since the frames will be encoded as CELT. Set a low target bitrate to get // consistent behaviour across platforms. (*encoder_ptr)->OnReceivedTargetAudioBitrate(24000); }); expects[static_cast(AudioFrameType::kEmptyFrame)] = 1; expects[static_cast(AudioFrameType::kAudioFrameSpeech)] = 1; expects[static_cast(AudioFrameType::kAudioFrameCN)] = 1; Run(webrtc::test::ResourcePath("audio_coding/teststereo32kHz", "pcm"), 32000, 2, out_filename, true, expects); } } // namespace webrtc