284 lines
10 KiB
C++
284 lines
10 KiB
C++
|
/*
|
||
|
* 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. 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<AudioEncoder>* encoder_ptr) {
|
||
|
(*encoder_ptr)->SetDtx(false);
|
||
|
});
|
||
|
|
||
|
expects[static_cast<int>(AudioFrameType::kEmptyFrame)] = 0;
|
||
|
expects[static_cast<int>(AudioFrameType::kAudioFrameSpeech)] = 1;
|
||
|
expects[static_cast<int>(AudioFrameType::kAudioFrameCN)] = 1;
|
||
|
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::kAudioFrameSpeech)] = 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::kAudioFrameSpeech)] = 1;
|
||
|
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::kAudioFrameSpeech)] = 1;
|
||
|
expects[static_cast<int>(AudioFrameType::kAudioFrameCN)] = 1;
|
||
|
Run(webrtc::test::ResourcePath("audio_coding/teststereo32kHz", "pcm"), 32000,
|
||
|
2, out_filename, true, expects);
|
||
|
}
|
||
|
|
||
|
} // namespace webrtc
|