Source code

Revision control

Copy as Markdown

Other Tools

/*
* Copyright (c) 2016 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/neteq/tools/neteq_test.h"
#include <iomanip>
#include <iostream>
#include "api/environment/environment.h"
#include "api/environment/environment_factory.h"
#include "api/field_trials.h"
#include "api/units/timestamp.h"
#include "modules/audio_coding/neteq/default_neteq_factory.h"
#include "modules/rtp_rtcp/source/byte_io.h"
#include "system_wrappers/include/clock.h"
namespace webrtc {
namespace test {
namespace {
std::optional<NetEq::Operation> ActionToOperations(
std::optional<NetEqSimulator::Action> a) {
if (!a) {
return std::nullopt;
}
switch (*a) {
case NetEqSimulator::Action::kAccelerate:
return std::make_optional(NetEq::Operation::kAccelerate);
case NetEqSimulator::Action::kExpand:
return std::make_optional(NetEq::Operation::kExpand);
case NetEqSimulator::Action::kNormal:
return std::make_optional(NetEq::Operation::kNormal);
case NetEqSimulator::Action::kPreemptiveExpand:
return std::make_optional(NetEq::Operation::kPreemptiveExpand);
}
}
std::unique_ptr<NetEq> CreateNetEq(
const Environment& env,
const NetEq::Config& config,
scoped_refptr<AudioDecoderFactory> decoder_factory) {
return DefaultNetEqFactory().Create(env, config, std::move(decoder_factory));
}
} // namespace
void DefaultNetEqTestErrorCallback::OnInsertPacketError(
const NetEqInput::PacketData& packet) {
std::cerr << "InsertPacket returned an error." << std::endl;
std::cerr << "Packet data: " << packet.ToString() << std::endl;
RTC_FATAL();
}
void DefaultNetEqTestErrorCallback::OnGetAudioError() {
std::cerr << "GetAudio returned an error." << std::endl;
RTC_FATAL();
}
NetEqTest::NetEqTest(const NetEq::Config& config,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
const DecoderMap& codecs,
std::unique_ptr<std::ofstream> text_log,
NetEqFactory* neteq_factory,
std::unique_ptr<NetEqInput> input,
std::unique_ptr<AudioSink> output,
Callbacks callbacks,
absl::string_view field_trials)
: input_(std::move(input)),
clock_(Timestamp::Millis(input_->NextEventTime().value_or(0))),
env_(CreateEnvironment(
&clock_,
FieldTrials::CreateNoGlobal(std::string(field_trials)))),
neteq_(
neteq_factory
? neteq_factory->Create(env_, config, std::move(decoder_factory))
: CreateNetEq(env_, config, std::move(decoder_factory))),
output_(std::move(output)),
callbacks_(callbacks),
sample_rate_hz_(config.sample_rate_hz),
text_log_(std::move(text_log)) {
RegisterDecoders(codecs);
}
NetEqTest::~NetEqTest() = default;
int64_t NetEqTest::Run() {
int64_t simulation_time = 0;
SimulationStepResult step_result;
do {
step_result = RunToNextGetAudio();
simulation_time += step_result.simulation_step_ms;
} while (!step_result.is_simulation_finished);
if (callbacks_.simulation_ended_callback) {
callbacks_.simulation_ended_callback->SimulationEnded(simulation_time);
}
return simulation_time;
}
NetEqTest::SimulationStepResult NetEqTest::RunToNextGetAudio() {
SimulationStepResult result;
const int64_t start_time_ms = *input_->NextEventTime();
int64_t time_now_ms = clock_.CurrentTime().ms();
current_state_.packet_iat_ms.clear();
while (!input_->ended()) {
// Advance time to next event.
RTC_DCHECK(input_->NextEventTime());
clock_.AdvanceTimeMilliseconds(*input_->NextEventTime() - time_now_ms);
time_now_ms = *input_->NextEventTime();
// Check if it is time to insert packet.
if (input_->NextPacketTime() && time_now_ms >= *input_->NextPacketTime()) {
std::unique_ptr<NetEqInput::PacketData> packet_data = input_->PopPacket();
RTC_CHECK(packet_data);
const size_t payload_data_length =
packet_data->payload.size() - packet_data->header.paddingLength;
if (payload_data_length != 0) {
int error = neteq_->InsertPacket(
packet_data->header,
rtc::ArrayView<const uint8_t>(packet_data->payload),
Timestamp::Millis(time_now_ms));
if (error != NetEq::kOK && callbacks_.error_callback) {
callbacks_.error_callback->OnInsertPacketError(*packet_data);
}
if (callbacks_.post_insert_packet) {
callbacks_.post_insert_packet->AfterInsertPacket(*packet_data,
neteq_.get());
}
} else {
neteq_->InsertEmptyPacket(packet_data->header);
}
if (last_packet_time_ms_) {
current_state_.packet_iat_ms.push_back(time_now_ms -
*last_packet_time_ms_);
}
if (text_log_) {
const auto ops_state = neteq_->GetOperationsAndState();
const auto delta_wallclock =
last_packet_time_ms_ ? (time_now_ms - *last_packet_time_ms_) : -1;
const auto delta_timestamp =
last_packet_timestamp_
? (static_cast<int64_t>(packet_data->header.timestamp) -
*last_packet_timestamp_) *
1000 / sample_rate_hz_
: -1;
const auto packet_size_bytes =
packet_data->payload.size() == 12
? ByteReader<uint32_t>::ReadLittleEndian(
&packet_data->payload[8])
: -1;
*text_log_ << "Packet - wallclock: " << std::setw(5) << time_now_ms
<< ", delta wc: " << std::setw(4) << delta_wallclock
<< ", seq_no: " << packet_data->header.sequenceNumber
<< ", timestamp: " << std::setw(10)
<< packet_data->header.timestamp
<< ", delta ts: " << std::setw(4) << delta_timestamp
<< ", size: " << std::setw(5) << packet_size_bytes
<< ", frame size: " << std::setw(3)
<< ops_state.current_frame_size_ms
<< ", buffer size: " << std::setw(4)
<< ops_state.current_buffer_size_ms << std::endl;
}
last_packet_time_ms_ = std::make_optional<int>(time_now_ms);
last_packet_timestamp_ =
std::make_optional<uint32_t>(packet_data->header.timestamp);
}
if (input_->NextSetMinimumDelayInfo().has_value() &&
time_now_ms >= input_->NextSetMinimumDelayInfo().value().timestamp_ms) {
neteq_->SetBaseMinimumDelayMs(
input_->NextSetMinimumDelayInfo().value().delay_ms);
input_->AdvanceSetMinimumDelay();
}
// Check if it is time to get output audio.
if (input_->NextOutputEventTime() &&
time_now_ms >= *input_->NextOutputEventTime()) {
if (callbacks_.get_audio_callback) {
callbacks_.get_audio_callback->BeforeGetAudio(neteq_.get());
}
AudioFrame out_frame;
int error = neteq_->GetAudio(&out_frame, nullptr, nullptr,
ActionToOperations(next_action_));
next_action_ = std::nullopt;
if (error != NetEq::kOK) {
if (callbacks_.error_callback) {
callbacks_.error_callback->OnGetAudioError();
}
} else {
sample_rate_hz_ = out_frame.sample_rate_hz_;
}
if (callbacks_.get_audio_callback) {
callbacks_.get_audio_callback->AfterGetAudio(
time_now_ms, out_frame, out_frame.muted(), neteq_.get());
}
if (output_) {
RTC_CHECK(output_->WriteArray(
out_frame.data(),
out_frame.samples_per_channel_ * out_frame.num_channels_));
}
input_->AdvanceOutputEvent();
result.simulation_step_ms =
input_->NextEventTime().value_or(time_now_ms) - start_time_ms;
const auto operations_state = neteq_->GetOperationsAndState();
current_state_.current_delay_ms = operations_state.current_buffer_size_ms;
current_state_.packet_size_ms = operations_state.current_frame_size_ms;
current_state_.next_packet_available =
operations_state.next_packet_available;
current_state_.packet_buffer_flushed =
operations_state.packet_buffer_flushes >
prev_ops_state_.packet_buffer_flushes;
// TODO(ivoc): Add more accurate reporting by tracking the origin of
// samples in the sync buffer.
result.action_times_ms[Action::kExpand] = 0;
result.action_times_ms[Action::kAccelerate] = 0;
result.action_times_ms[Action::kPreemptiveExpand] = 0;
result.action_times_ms[Action::kNormal] = 0;
if (out_frame.speech_type_ == AudioFrame::SpeechType::kPLC ||
out_frame.speech_type_ == AudioFrame::SpeechType::kPLCCNG) {
// Consider the whole frame to be the result of expansion.
result.action_times_ms[Action::kExpand] = 10;
} else if (operations_state.accelerate_samples -
prev_ops_state_.accelerate_samples >
0) {
// Consider the whole frame to be the result of acceleration.
result.action_times_ms[Action::kAccelerate] = 10;
} else if (operations_state.preemptive_samples -
prev_ops_state_.preemptive_samples >
0) {
// Consider the whole frame to be the result of preemptive expansion.
result.action_times_ms[Action::kPreemptiveExpand] = 10;
} else {
// Consider the whole frame to be the result of normal playout.
result.action_times_ms[Action::kNormal] = 10;
}
auto lifetime_stats = LifetimeStats();
if (text_log_) {
const bool plc =
(out_frame.speech_type_ == AudioFrame::SpeechType::kPLC) ||
(out_frame.speech_type_ == AudioFrame::SpeechType::kPLCCNG);
const bool cng = out_frame.speech_type_ == AudioFrame::SpeechType::kCNG;
const bool voice_concealed =
(lifetime_stats.concealed_samples -
lifetime_stats.silent_concealed_samples) >
(prev_lifetime_stats_.concealed_samples -
prev_lifetime_stats_.silent_concealed_samples);
*text_log_ << "GetAudio - wallclock: " << std::setw(5) << time_now_ms
<< ", delta wc: " << std::setw(4)
<< (input_->NextEventTime().value_or(time_now_ms) -
start_time_ms)
<< ", CNG: " << cng << ", PLC: " << plc
<< ", voice concealed: " << voice_concealed
<< ", buffer size: " << std::setw(4)
<< current_state_.current_delay_ms << std::endl;
if (lifetime_stats.packets_discarded >
prev_lifetime_stats_.packets_discarded) {
*text_log_ << "Discarded "
<< (lifetime_stats.packets_discarded -
prev_lifetime_stats_.packets_discarded)
<< " primary packets." << std::endl;
}
if (operations_state.packet_buffer_flushes >
prev_ops_state_.packet_buffer_flushes) {
*text_log_ << "Flushed packet buffer "
<< (operations_state.packet_buffer_flushes -
prev_ops_state_.packet_buffer_flushes)
<< " times." << std::endl;
}
}
prev_lifetime_stats_ = lifetime_stats;
const bool no_more_packets_to_decode =
!input_->NextPacketTime() && !operations_state.next_packet_available;
result.is_simulation_finished =
no_more_packets_to_decode || input_->ended();
prev_ops_state_ = operations_state;
return result;
}
}
result.simulation_step_ms =
input_->NextEventTime().value_or(time_now_ms) - start_time_ms;
result.is_simulation_finished = true;
return result;
}
void NetEqTest::SetNextAction(NetEqTest::Action next_operation) {
next_action_ = std::optional<Action>(next_operation);
}
NetEqTest::NetEqState NetEqTest::GetNetEqState() {
return current_state_;
}
NetEqNetworkStatistics NetEqTest::SimulationStats() {
NetEqNetworkStatistics stats;
RTC_CHECK_EQ(neteq_->NetworkStatistics(&stats), 0);
return stats;
}
NetEqLifetimeStatistics NetEqTest::LifetimeStats() const {
return neteq_->GetLifetimeStatistics();
}
NetEqTest::DecoderMap NetEqTest::StandardDecoderMap() {
DecoderMap codecs = {{0, SdpAudioFormat("pcmu", 8000, 1)},
{8, SdpAudioFormat("pcma", 8000, 1)},
#ifdef WEBRTC_CODEC_ILBC
{102, SdpAudioFormat("ilbc", 8000, 1)},
#endif
#ifdef WEBRTC_CODEC_OPUS
{111, SdpAudioFormat("opus", 48000, 2)},
{63, SdpAudioFormat("red", 48000, 2)},
#endif
{93, SdpAudioFormat("l16", 8000, 1)},
{94, SdpAudioFormat("l16", 16000, 1)},
{95, SdpAudioFormat("l16", 32000, 1)},
{96, SdpAudioFormat("l16", 48000, 1)},
{9, SdpAudioFormat("g722", 8000, 1)},
{106, SdpAudioFormat("telephone-event", 8000, 1)},
{114, SdpAudioFormat("telephone-event", 16000, 1)},
{115, SdpAudioFormat("telephone-event", 32000, 1)},
{116, SdpAudioFormat("telephone-event", 48000, 1)},
{117, SdpAudioFormat("red", 8000, 1)},
{13, SdpAudioFormat("cn", 8000, 1)},
{98, SdpAudioFormat("cn", 16000, 1)},
{99, SdpAudioFormat("cn", 32000, 1)},
{100, SdpAudioFormat("cn", 48000, 1)}};
return codecs;
}
void NetEqTest::RegisterDecoders(const DecoderMap& codecs) {
for (const auto& c : codecs) {
RTC_CHECK(neteq_->RegisterPayloadType(c.first, c.second))
<< "Cannot register " << c.second.name << " to payload type "
<< c.first;
}
}
} // namespace test
} // namespace webrtc