| /* | 
 |  *  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 <string> | 
 |  | 
 | #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<int>(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<int>(AudioFrameType::kEmptyFrame)]); | 
 |   printf("kAudioFrameSpeech %u\n", | 
 |          counter_[static_cast<int>(AudioFrameType::kAudioFrameSpeech)]); | 
 |   printf("kAudioFrameCN     %u\n", | 
 |          counter_[static_cast<int>(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<AudioEncoderIlbc, | 
 |                                                  AudioEncoderIsacFloat, | 
 |                                                  AudioEncoderOpus>()), | 
 |       decoder_factory_(CreateAudioDecoderFactory<AudioDecoderIlbc, | 
 |                                                  AudioDecoderIsacFloat, | 
 |                                                  AudioDecoderOpus>()), | 
 |       acm_send_(AudioCodingModule::Create( | 
 |           AudioCodingModule::Config(decoder_factory_))), | 
 |       acm_receive_(AudioCodingModule::Create( | 
 |           AudioCodingModule::Config(decoder_factory_))), | 
 |       channel_(std::make_unique<Channel>()), | 
 |       packetization_callback_( | 
 |           std::make_unique<MonitoringAudioPacketizationCallback>( | 
 |               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::Aggressiveness> 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<int, SdpAudioFormat> 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."; | 
 |         break; | 
 |       } | 
 |       case 1: { | 
 |         EXPECT_GT(st, 0u) << "stats[" << i << "] error."; | 
 |         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) { | 
 |   Test(/*new_outfile=*/true, | 
 |        /*expect_dtx_enabled=*/RegisterCodec(codec_format, absl::nullopt)); | 
 |  | 
 |   Test(/*new_outfile=*/false, | 
 |        /*expect_dtx_enabled=*/RegisterCodec(codec_format, Vad::kVadAggressive)); | 
 |  | 
 |   Test(/*new_outfile=*/false, | 
 |        /*expect_dtx_enabled=*/RegisterCodec(codec_format, Vad::kVadLowBitrate)); | 
 |  | 
 |   Test(/*new_outfile=*/false, /*expect_dtx_enabled=*/RegisterCodec( | 
 |            codec_format, Vad::kVadVeryAggressive)); | 
 |  | 
 |   Test(/*new_outfile=*/false, | 
 |        /*expect_dtx_enabled=*/RegisterCodec(codec_format, Vad::kVadNormal)); | 
 | } | 
 |  | 
 | // Set the expectation and run the test. | 
 | void TestWebRtcVadDtx::Test(bool new_outfile, bool expect_dtx_enabled) { | 
 |   int expects[] = {-1, 1, expect_dtx_enabled, 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, 1, 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<AudioEncoder>* encoder_ptr) { | 
 |     (*encoder_ptr)->SetDtx(false); | 
 |   }); | 
 |  | 
 |   Run(webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"), 32000, 1, | 
 |       out_filename, false, expects); | 
 |  | 
 |   acm_send_->ModifyEncoder([](std::unique_ptr<AudioEncoder>* encoder_ptr) { | 
 |     (*encoder_ptr)->SetDtx(true); | 
 |   }); | 
 |   expects[static_cast<int>(AudioFrameType::kEmptyFrame)] = 1; | 
 |   expects[static_cast<int>(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<AudioEncoder>* encoder_ptr) { | 
 |     (*encoder_ptr)->SetDtx(false); | 
 |   }); | 
 |   expects[static_cast<int>(AudioFrameType::kEmptyFrame)] = 0; | 
 |   expects[static_cast<int>(AudioFrameType::kAudioFrameCN)] = 0; | 
 |   Run(webrtc::test::ResourcePath("audio_coding/teststereo32kHz", "pcm"), 32000, | 
 |       2, out_filename, false, expects); | 
 |  | 
 |   acm_send_->ModifyEncoder([](std::unique_ptr<AudioEncoder>* 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<int>(AudioFrameType::kEmptyFrame)] = 1; | 
 |   expects[static_cast<int>(AudioFrameType::kAudioFrameCN)] = 1; | 
 |   Run(webrtc::test::ResourcePath("audio_coding/teststereo32kHz", "pcm"), 32000, | 
 |       2, out_filename, true, expects); | 
 | } | 
 |  | 
 | }  // namespace webrtc |