Merge branch 'upstream-master'

Bug: 153469641
Test: none
Change-Id: Ic33e363deb0d1ac4bb4d57c3c239eb2e45370802
This commit is contained in:
Jorge E. Moreira
2020-07-17 14:12:45 -07:00
9929 changed files with 1031155 additions and 779502 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
include_rules = [
"+call",
"+common_audio",
"+logging/rtc_event_log",
"+audio_coding/neteq/neteq_unittest.pb.h", # Different path.
"+system_wrappers",
]

View File

@ -0,0 +1,4 @@
henrik.lundin@webrtc.org
kwiberg@webrtc.org
minyue@webrtc.org
ivoc@webrtc.org

View File

@ -0,0 +1,161 @@
/*
* Copyright (c) 2014 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_receive_test.h"
#include <stdio.h>
#include <memory>
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "modules/audio_coding/include/audio_coding_module.h"
#include "modules/audio_coding/neteq/tools/audio_sink.h"
#include "modules/audio_coding/neteq/tools/packet.h"
#include "modules/audio_coding/neteq/tools/packet_source.h"
#include "test/gtest.h"
namespace webrtc {
namespace test {
namespace {
AudioCodingModule::Config MakeAcmConfig(
Clock* clock,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory) {
AudioCodingModule::Config config;
config.clock = clock;
config.decoder_factory = std::move(decoder_factory);
return config;
}
} // namespace
AcmReceiveTestOldApi::AcmReceiveTestOldApi(
PacketSource* packet_source,
AudioSink* audio_sink,
int output_freq_hz,
NumOutputChannels exptected_output_channels,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory)
: clock_(0),
acm_(webrtc::AudioCodingModule::Create(
MakeAcmConfig(&clock_, std::move(decoder_factory)))),
packet_source_(packet_source),
audio_sink_(audio_sink),
output_freq_hz_(output_freq_hz),
exptected_output_channels_(exptected_output_channels) {}
AcmReceiveTestOldApi::~AcmReceiveTestOldApi() = default;
void AcmReceiveTestOldApi::RegisterDefaultCodecs() {
acm_->SetReceiveCodecs({{103, {"ISAC", 16000, 1}},
{104, {"ISAC", 32000, 1}},
{107, {"L16", 8000, 1}},
{108, {"L16", 16000, 1}},
{109, {"L16", 32000, 1}},
{111, {"L16", 8000, 2}},
{112, {"L16", 16000, 2}},
{113, {"L16", 32000, 2}},
{0, {"PCMU", 8000, 1}},
{110, {"PCMU", 8000, 2}},
{8, {"PCMA", 8000, 1}},
{118, {"PCMA", 8000, 2}},
{102, {"ILBC", 8000, 1}},
{9, {"G722", 8000, 1}},
{119, {"G722", 8000, 2}},
{120, {"OPUS", 48000, 2, {{"stereo", "1"}}}},
{13, {"CN", 8000, 1}},
{98, {"CN", 16000, 1}},
{99, {"CN", 32000, 1}}});
}
// Remaps payload types from ACM's default to those used in the resource file
// neteq_universal_new.rtp.
void AcmReceiveTestOldApi::RegisterNetEqTestCodecs() {
acm_->SetReceiveCodecs({{103, {"ISAC", 16000, 1}},
{104, {"ISAC", 32000, 1}},
{93, {"L16", 8000, 1}},
{94, {"L16", 16000, 1}},
{95, {"L16", 32000, 1}},
{0, {"PCMU", 8000, 1}},
{8, {"PCMA", 8000, 1}},
{102, {"ILBC", 8000, 1}},
{9, {"G722", 8000, 1}},
{120, {"OPUS", 48000, 2}},
{13, {"CN", 8000, 1}},
{98, {"CN", 16000, 1}},
{99, {"CN", 32000, 1}}});
}
void AcmReceiveTestOldApi::Run() {
for (std::unique_ptr<Packet> packet(packet_source_->NextPacket()); packet;
packet = packet_source_->NextPacket()) {
// Pull audio until time to insert packet.
while (clock_.TimeInMilliseconds() < packet->time_ms()) {
AudioFrame output_frame;
bool muted;
EXPECT_EQ(0,
acm_->PlayoutData10Ms(output_freq_hz_, &output_frame, &muted));
ASSERT_EQ(output_freq_hz_, output_frame.sample_rate_hz_);
ASSERT_FALSE(muted);
const size_t samples_per_block =
static_cast<size_t>(output_freq_hz_ * 10 / 1000);
EXPECT_EQ(samples_per_block, output_frame.samples_per_channel_);
if (exptected_output_channels_ != kArbitraryChannels) {
if (output_frame.speech_type_ == webrtc::AudioFrame::kPLC) {
// Don't check number of channels for PLC output, since each test run
// usually starts with a short period of mono PLC before decoding the
// first packet.
} else {
EXPECT_EQ(exptected_output_channels_, output_frame.num_channels_);
}
}
ASSERT_TRUE(audio_sink_->WriteAudioFrame(output_frame));
clock_.AdvanceTimeMilliseconds(10);
AfterGetAudio();
}
EXPECT_EQ(0, acm_->IncomingPacket(
packet->payload(),
static_cast<int32_t>(packet->payload_length_bytes()),
packet->header()))
<< "Failure when inserting packet:" << std::endl
<< " PT = " << static_cast<int>(packet->header().payloadType)
<< std::endl
<< " TS = " << packet->header().timestamp << std::endl
<< " SN = " << packet->header().sequenceNumber;
}
}
AcmReceiveTestToggleOutputFreqOldApi::AcmReceiveTestToggleOutputFreqOldApi(
PacketSource* packet_source,
AudioSink* audio_sink,
int output_freq_hz_1,
int output_freq_hz_2,
int toggle_period_ms,
NumOutputChannels exptected_output_channels)
: AcmReceiveTestOldApi(packet_source,
audio_sink,
output_freq_hz_1,
exptected_output_channels,
CreateBuiltinAudioDecoderFactory()),
output_freq_hz_1_(output_freq_hz_1),
output_freq_hz_2_(output_freq_hz_2),
toggle_period_ms_(toggle_period_ms),
last_toggle_time_ms_(clock_.TimeInMilliseconds()) {}
void AcmReceiveTestToggleOutputFreqOldApi::AfterGetAudio() {
if (clock_.TimeInMilliseconds() >= last_toggle_time_ms_ + toggle_period_ms_) {
output_freq_hz_ = (output_freq_hz_ == output_freq_hz_1_)
? output_freq_hz_2_
: output_freq_hz_1_;
last_toggle_time_ms_ = clock_.TimeInMilliseconds();
}
}
} // namespace test
} // namespace webrtc

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) 2014 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.
*/
#ifndef MODULES_AUDIO_CODING_ACM2_ACM_RECEIVE_TEST_H_
#define MODULES_AUDIO_CODING_ACM2_ACM_RECEIVE_TEST_H_
#include <stddef.h> // for size_t
#include <memory>
#include <string>
#include "api/audio_codecs/audio_decoder_factory.h"
#include "api/scoped_refptr.h"
#include "rtc_base/constructor_magic.h"
#include "system_wrappers/include/clock.h"
namespace webrtc {
class AudioCodingModule;
class AudioDecoder;
namespace test {
class AudioSink;
class PacketSource;
class AcmReceiveTestOldApi {
public:
enum NumOutputChannels : size_t {
kArbitraryChannels = 0,
kMonoOutput = 1,
kStereoOutput = 2,
kQuadOutput = 4
};
AcmReceiveTestOldApi(PacketSource* packet_source,
AudioSink* audio_sink,
int output_freq_hz,
NumOutputChannels exptected_output_channels,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory);
virtual ~AcmReceiveTestOldApi();
// Registers the codecs with default parameters from ACM.
void RegisterDefaultCodecs();
// Registers codecs with payload types matching the pre-encoded NetEq test
// files.
void RegisterNetEqTestCodecs();
// Runs the test and returns true if successful.
void Run();
AudioCodingModule* get_acm() { return acm_.get(); }
protected:
// Method is called after each block of output audio is received from ACM.
virtual void AfterGetAudio() {}
SimulatedClock clock_;
std::unique_ptr<AudioCodingModule> acm_;
PacketSource* packet_source_;
AudioSink* audio_sink_;
int output_freq_hz_;
NumOutputChannels exptected_output_channels_;
RTC_DISALLOW_COPY_AND_ASSIGN(AcmReceiveTestOldApi);
};
// This test toggles the output frequency every |toggle_period_ms|. The test
// starts with |output_freq_hz_1|. Except for the toggling, it does the same
// thing as AcmReceiveTestOldApi.
class AcmReceiveTestToggleOutputFreqOldApi : public AcmReceiveTestOldApi {
public:
AcmReceiveTestToggleOutputFreqOldApi(
PacketSource* packet_source,
AudioSink* audio_sink,
int output_freq_hz_1,
int output_freq_hz_2,
int toggle_period_ms,
NumOutputChannels exptected_output_channels);
protected:
void AfterGetAudio() override;
const int output_freq_hz_1_;
const int output_freq_hz_2_;
const int toggle_period_ms_;
int64_t last_toggle_time_ms_;
};
} // namespace test
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_ACM2_ACM_RECEIVE_TEST_H_

View File

@ -0,0 +1,336 @@
/*
* 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 <stdlib.h>
#include <string.h>
#include <cstdint>
#include <vector>
#include "absl/strings/match.h"
#include "api/audio/audio_frame.h"
#include "api/audio_codecs/audio_decoder.h"
#include "api/neteq/neteq.h"
#include "modules/audio_coding/acm2/acm_resampler.h"
#include "modules/audio_coding/acm2/call_statistics.h"
#include "modules/audio_coding/neteq/default_neteq_factory.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/strings/audio_format_to_string.h"
#include "system_wrappers/include/clock.h"
namespace webrtc {
namespace acm2 {
namespace {
std::unique_ptr<NetEq> CreateNetEq(
NetEqFactory* neteq_factory,
const NetEq::Config& config,
Clock* clock,
const rtc::scoped_refptr<AudioDecoderFactory>& decoder_factory) {
if (neteq_factory) {
return neteq_factory->CreateNetEq(config, decoder_factory, clock);
}
return DefaultNetEqFactory().CreateNetEq(config, decoder_factory, clock);
}
} // namespace
AcmReceiver::AcmReceiver(const AudioCodingModule::Config& config)
: last_audio_buffer_(new int16_t[AudioFrame::kMaxDataSizeSamples]),
neteq_(CreateNetEq(config.neteq_factory,
config.neteq_config,
config.clock,
config.decoder_factory)),
clock_(config.clock),
resampled_last_output_frame_(true) {
RTC_DCHECK(clock_);
memset(last_audio_buffer_.get(), 0,
sizeof(int16_t) * AudioFrame::kMaxDataSizeSamples);
}
AcmReceiver::~AcmReceiver() = default;
int AcmReceiver::SetMinimumDelay(int delay_ms) {
if (neteq_->SetMinimumDelay(delay_ms))
return 0;
RTC_LOG(LERROR) << "AcmReceiver::SetExtraDelay " << delay_ms;
return -1;
}
int AcmReceiver::SetMaximumDelay(int delay_ms) {
if (neteq_->SetMaximumDelay(delay_ms))
return 0;
RTC_LOG(LERROR) << "AcmReceiver::SetExtraDelay " << delay_ms;
return -1;
}
bool AcmReceiver::SetBaseMinimumDelayMs(int delay_ms) {
return neteq_->SetBaseMinimumDelayMs(delay_ms);
}
int AcmReceiver::GetBaseMinimumDelayMs() const {
return neteq_->GetBaseMinimumDelayMs();
}
absl::optional<int> AcmReceiver::last_packet_sample_rate_hz() const {
rtc::CritScope lock(&crit_sect_);
if (!last_decoder_) {
return absl::nullopt;
}
return last_decoder_->sample_rate_hz;
}
int AcmReceiver::last_output_sample_rate_hz() const {
return neteq_->last_output_sample_rate_hz();
}
int AcmReceiver::InsertPacket(const RTPHeader& rtp_header,
rtc::ArrayView<const uint8_t> incoming_payload) {
if (incoming_payload.empty()) {
neteq_->InsertEmptyPacket(rtp_header);
return 0;
}
int payload_type = rtp_header.payloadType;
auto format = neteq_->GetDecoderFormat(payload_type);
if (format && absl::EqualsIgnoreCase(format->sdp_format.name, "red")) {
// This is a RED packet. Get the format of the audio codec.
payload_type = incoming_payload[0] & 0x7f;
format = neteq_->GetDecoderFormat(payload_type);
}
if (!format) {
RTC_LOG_F(LS_ERROR) << "Payload-type " << payload_type
<< " is not registered.";
return -1;
}
{
rtc::CritScope lock(&crit_sect_);
if (absl::EqualsIgnoreCase(format->sdp_format.name, "cn")) {
if (last_decoder_ && last_decoder_->num_channels > 1) {
// This is a CNG and the audio codec is not mono, so skip pushing in
// packets into NetEq.
return 0;
}
} else {
last_decoder_ = DecoderInfo{/*payload_type=*/payload_type,
/*sample_rate_hz=*/format->sample_rate_hz,
/*num_channels=*/format->num_channels,
/*sdp_format=*/std::move(format->sdp_format)};
}
} // |crit_sect_| is released.
if (neteq_->InsertPacket(rtp_header, incoming_payload) < 0) {
RTC_LOG(LERROR) << "AcmReceiver::InsertPacket "
<< static_cast<int>(rtp_header.payloadType)
<< " Failed to insert packet";
return -1;
}
return 0;
}
int AcmReceiver::GetAudio(int desired_freq_hz,
AudioFrame* audio_frame,
bool* muted) {
RTC_DCHECK(muted);
// Accessing members, take the lock.
rtc::CritScope lock(&crit_sect_);
if (neteq_->GetAudio(audio_frame, muted) != NetEq::kOK) {
RTC_LOG(LERROR) << "AcmReceiver::GetAudio - NetEq Failed.";
return -1;
}
const int current_sample_rate_hz = neteq_->last_output_sample_rate_hz();
// Update if resampling is required.
const bool need_resampling =
(desired_freq_hz != -1) && (current_sample_rate_hz != desired_freq_hz);
if (need_resampling && !resampled_last_output_frame_) {
// Prime the resampler with the last frame.
int16_t temp_output[AudioFrame::kMaxDataSizeSamples];
int samples_per_channel_int = resampler_.Resample10Msec(
last_audio_buffer_.get(), current_sample_rate_hz, desired_freq_hz,
audio_frame->num_channels_, AudioFrame::kMaxDataSizeSamples,
temp_output);
if (samples_per_channel_int < 0) {
RTC_LOG(LERROR) << "AcmReceiver::GetAudio - "
"Resampling last_audio_buffer_ failed.";
return -1;
}
}
// TODO(henrik.lundin) Glitches in the output may appear if the output rate
// from NetEq changes. See WebRTC issue 3923.
if (need_resampling) {
// TODO(yujo): handle this more efficiently for muted frames.
int samples_per_channel_int = resampler_.Resample10Msec(
audio_frame->data(), current_sample_rate_hz, desired_freq_hz,
audio_frame->num_channels_, AudioFrame::kMaxDataSizeSamples,
audio_frame->mutable_data());
if (samples_per_channel_int < 0) {
RTC_LOG(LERROR)
<< "AcmReceiver::GetAudio - Resampling audio_buffer_ failed.";
return -1;
}
audio_frame->samples_per_channel_ =
static_cast<size_t>(samples_per_channel_int);
audio_frame->sample_rate_hz_ = desired_freq_hz;
RTC_DCHECK_EQ(
audio_frame->sample_rate_hz_,
rtc::dchecked_cast<int>(audio_frame->samples_per_channel_ * 100));
resampled_last_output_frame_ = true;
} else {
resampled_last_output_frame_ = false;
// We might end up here ONLY if codec is changed.
}
// Store current audio in |last_audio_buffer_| for next time.
memcpy(last_audio_buffer_.get(), audio_frame->data(),
sizeof(int16_t) * audio_frame->samples_per_channel_ *
audio_frame->num_channels_);
call_stats_.DecodedByNetEq(audio_frame->speech_type_, *muted);
return 0;
}
void AcmReceiver::SetCodecs(const std::map<int, SdpAudioFormat>& codecs) {
neteq_->SetCodecs(codecs);
}
void AcmReceiver::FlushBuffers() {
neteq_->FlushBuffers();
}
void AcmReceiver::RemoveAllCodecs() {
rtc::CritScope lock(&crit_sect_);
neteq_->RemoveAllPayloadTypes();
last_decoder_ = absl::nullopt;
}
absl::optional<uint32_t> AcmReceiver::GetPlayoutTimestamp() {
return neteq_->GetPlayoutTimestamp();
}
int AcmReceiver::FilteredCurrentDelayMs() const {
return neteq_->FilteredCurrentDelayMs();
}
int AcmReceiver::TargetDelayMs() const {
return neteq_->TargetDelayMs();
}
absl::optional<std::pair<int, SdpAudioFormat>> AcmReceiver::LastDecoder()
const {
rtc::CritScope lock(&crit_sect_);
if (!last_decoder_) {
return absl::nullopt;
}
RTC_DCHECK_NE(-1, last_decoder_->payload_type);
return std::make_pair(last_decoder_->payload_type, last_decoder_->sdp_format);
}
void AcmReceiver::GetNetworkStatistics(NetworkStatistics* acm_stat) const {
NetEqNetworkStatistics neteq_stat;
// NetEq function always returns zero, so we don't check the return value.
neteq_->NetworkStatistics(&neteq_stat);
acm_stat->currentBufferSize = neteq_stat.current_buffer_size_ms;
acm_stat->preferredBufferSize = neteq_stat.preferred_buffer_size_ms;
acm_stat->jitterPeaksFound = neteq_stat.jitter_peaks_found ? true : false;
acm_stat->currentPacketLossRate = neteq_stat.packet_loss_rate;
acm_stat->currentExpandRate = neteq_stat.expand_rate;
acm_stat->currentSpeechExpandRate = neteq_stat.speech_expand_rate;
acm_stat->currentPreemptiveRate = neteq_stat.preemptive_rate;
acm_stat->currentAccelerateRate = neteq_stat.accelerate_rate;
acm_stat->currentSecondaryDecodedRate = neteq_stat.secondary_decoded_rate;
acm_stat->currentSecondaryDiscardedRate = neteq_stat.secondary_discarded_rate;
acm_stat->addedSamples = neteq_stat.added_zero_samples;
acm_stat->meanWaitingTimeMs = neteq_stat.mean_waiting_time_ms;
acm_stat->medianWaitingTimeMs = neteq_stat.median_waiting_time_ms;
acm_stat->minWaitingTimeMs = neteq_stat.min_waiting_time_ms;
acm_stat->maxWaitingTimeMs = neteq_stat.max_waiting_time_ms;
NetEqLifetimeStatistics neteq_lifetime_stat = neteq_->GetLifetimeStatistics();
acm_stat->totalSamplesReceived = neteq_lifetime_stat.total_samples_received;
acm_stat->concealedSamples = neteq_lifetime_stat.concealed_samples;
acm_stat->silentConcealedSamples =
neteq_lifetime_stat.silent_concealed_samples;
acm_stat->concealmentEvents = neteq_lifetime_stat.concealment_events;
acm_stat->jitterBufferDelayMs = neteq_lifetime_stat.jitter_buffer_delay_ms;
acm_stat->jitterBufferTargetDelayMs =
neteq_lifetime_stat.jitter_buffer_target_delay_ms;
acm_stat->jitterBufferEmittedCount =
neteq_lifetime_stat.jitter_buffer_emitted_count;
acm_stat->delayedPacketOutageSamples =
neteq_lifetime_stat.delayed_packet_outage_samples;
acm_stat->relativePacketArrivalDelayMs =
neteq_lifetime_stat.relative_packet_arrival_delay_ms;
acm_stat->interruptionCount = neteq_lifetime_stat.interruption_count;
acm_stat->totalInterruptionDurationMs =
neteq_lifetime_stat.total_interruption_duration_ms;
acm_stat->insertedSamplesForDeceleration =
neteq_lifetime_stat.inserted_samples_for_deceleration;
acm_stat->removedSamplesForAcceleration =
neteq_lifetime_stat.removed_samples_for_acceleration;
acm_stat->fecPacketsReceived = neteq_lifetime_stat.fec_packets_received;
acm_stat->fecPacketsDiscarded = neteq_lifetime_stat.fec_packets_discarded;
NetEqOperationsAndState neteq_operations_and_state =
neteq_->GetOperationsAndState();
acm_stat->packetBufferFlushes =
neteq_operations_and_state.packet_buffer_flushes;
}
int AcmReceiver::EnableNack(size_t max_nack_list_size) {
neteq_->EnableNack(max_nack_list_size);
return 0;
}
void AcmReceiver::DisableNack() {
neteq_->DisableNack();
}
std::vector<uint16_t> AcmReceiver::GetNackList(
int64_t round_trip_time_ms) const {
return neteq_->GetNackList(round_trip_time_ms);
}
void AcmReceiver::ResetInitialDelay() {
neteq_->SetMinimumDelay(0);
// TODO(turajs): Should NetEq Buffer be flushed?
}
uint32_t AcmReceiver::NowInTimestamp(int decoder_sampling_rate) const {
// Down-cast the time to (32-6)-bit since we only care about
// the least significant bits. (32-6) bits cover 2^(32-6) = 67108864 ms.
// We masked 6 most significant bits of 32-bit so there is no overflow in
// the conversion from milliseconds to timestamp.
const uint32_t now_in_ms =
static_cast<uint32_t>(clock_->TimeInMilliseconds() & 0x03ffffff);
return static_cast<uint32_t>((decoder_sampling_rate / 1000) * now_in_ms);
}
void AcmReceiver::GetDecodingCallStatistics(
AudioDecodingCallStats* stats) const {
rtc::CritScope lock(&crit_sect_);
*stats = call_stats_.GetDecodingStatistics();
}
} // namespace acm2
} // namespace webrtc

View File

@ -0,0 +1,229 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_ACM2_ACM_RECEIVER_H_
#define MODULES_AUDIO_CODING_ACM2_ACM_RECEIVER_H_
#include <stdint.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/audio_codecs/audio_decoder.h"
#include "api/audio_codecs/audio_format.h"
#include "modules/audio_coding/acm2/acm_resampler.h"
#include "modules/audio_coding/acm2/call_statistics.h"
#include "modules/audio_coding/include/audio_coding_module.h"
#include "rtc_base/critical_section.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
class Clock;
class NetEq;
struct RTPHeader;
namespace acm2 {
class AcmReceiver {
public:
// Constructor of the class
explicit AcmReceiver(const AudioCodingModule::Config& config);
// Destructor of the class.
~AcmReceiver();
//
// Inserts a payload with its associated RTP-header into NetEq.
//
// Input:
// - rtp_header : RTP header for the incoming payload containing
// information about payload type, sequence number,
// timestamp, SSRC and marker bit.
// - incoming_payload : Incoming audio payload.
// - length_payload : Length of incoming audio payload in bytes.
//
// Return value : 0 if OK.
// <0 if NetEq returned an error.
//
int InsertPacket(const RTPHeader& rtp_header,
rtc::ArrayView<const uint8_t> incoming_payload);
//
// Asks NetEq for 10 milliseconds of decoded audio.
//
// Input:
// -desired_freq_hz : specifies the sampling rate [Hz] of the output
// audio. If set -1 indicates to resampling is
// is required and the audio returned at the
// sampling rate of the decoder.
//
// Output:
// -audio_frame : an audio frame were output data and
// associated parameters are written to.
// -muted : if true, the sample data in audio_frame is not
// populated, and must be interpreted as all zero.
//
// Return value : 0 if OK.
// -1 if NetEq returned an error.
//
int GetAudio(int desired_freq_hz, AudioFrame* audio_frame, bool* muted);
// Replace the current set of decoders with the specified set.
void SetCodecs(const std::map<int, SdpAudioFormat>& codecs);
//
// Sets a minimum delay for packet buffer. The given delay is maintained,
// unless channel condition dictates a higher delay.
//
// Input:
// - delay_ms : minimum delay in milliseconds.
//
// Return value : 0 if OK.
// <0 if NetEq returned an error.
//
int SetMinimumDelay(int delay_ms);
//
// Sets a maximum delay [ms] for the packet buffer. The target delay does not
// exceed the given value, even if channel condition requires so.
//
// Input:
// - delay_ms : maximum delay in milliseconds.
//
// Return value : 0 if OK.
// <0 if NetEq returned an error.
//
int SetMaximumDelay(int delay_ms);
// Sets a base minimum delay in milliseconds for the packet buffer.
// Base minimum delay sets lower bound minimum delay value which
// is set via SetMinimumDelay.
//
// Returns true if value was successfully set, false overwise.
bool SetBaseMinimumDelayMs(int delay_ms);
// Returns current value of base minimum delay in milliseconds.
int GetBaseMinimumDelayMs() const;
//
// Resets the initial delay to zero.
//
void ResetInitialDelay();
// Returns the sample rate of the decoder associated with the last incoming
// packet. If no packet of a registered non-CNG codec has been received, the
// return value is empty. Also, if the decoder was unregistered since the last
// packet was inserted, the return value is empty.
absl::optional<int> last_packet_sample_rate_hz() const;
// Returns last_output_sample_rate_hz from the NetEq instance.
int last_output_sample_rate_hz() const;
//
// Get the current network statistics from NetEq.
//
// Output:
// - statistics : The current network statistics.
//
void GetNetworkStatistics(NetworkStatistics* statistics) const;
//
// Flushes the NetEq packet and speech buffers.
//
void FlushBuffers();
//
// Remove all registered codecs.
//
void RemoveAllCodecs();
// Returns the RTP timestamp for the last sample delivered by GetAudio().
// The return value will be empty if no valid timestamp is available.
absl::optional<uint32_t> GetPlayoutTimestamp();
// Returns the current total delay from NetEq (packet buffer and sync buffer)
// in ms, with smoothing applied to even out short-time fluctuations due to
// jitter. The packet buffer part of the delay is not updated during DTX/CNG
// periods.
//
int FilteredCurrentDelayMs() const;
// Returns the current target delay for NetEq in ms.
//
int TargetDelayMs() const;
//
// Get payload type and format of the last non-CNG/non-DTMF received payload.
// If no non-CNG/non-DTMF packet is received absl::nullopt is returned.
//
absl::optional<std::pair<int, SdpAudioFormat>> LastDecoder() const;
//
// Enable NACK and set the maximum size of the NACK list. If NACK is already
// enabled then the maximum NACK list size is modified accordingly.
//
// If the sequence number of last received packet is N, the sequence numbers
// of NACK list are in the range of [N - |max_nack_list_size|, N).
//
// |max_nack_list_size| should be positive (none zero) and less than or
// equal to |Nack::kNackListSizeLimit|. Otherwise, No change is applied and -1
// is returned. 0 is returned at success.
//
int EnableNack(size_t max_nack_list_size);
// Disable NACK.
void DisableNack();
//
// Get a list of packets to be retransmitted. |round_trip_time_ms| is an
// estimate of the round-trip-time (in milliseconds). Missing packets which
// will be playout in a shorter time than the round-trip-time (with respect
// to the time this API is called) will not be included in the list.
//
// Negative |round_trip_time_ms| results is an error message and empty list
// is returned.
//
std::vector<uint16_t> GetNackList(int64_t round_trip_time_ms) const;
//
// Get statistics of calls to GetAudio().
void GetDecodingCallStatistics(AudioDecodingCallStats* stats) const;
private:
struct DecoderInfo {
int payload_type;
int sample_rate_hz;
int num_channels;
SdpAudioFormat sdp_format;
};
uint32_t NowInTimestamp(int decoder_sampling_rate) const;
rtc::CriticalSection crit_sect_;
absl::optional<DecoderInfo> last_decoder_ RTC_GUARDED_BY(crit_sect_);
ACMResampler resampler_ RTC_GUARDED_BY(crit_sect_);
std::unique_ptr<int16_t[]> last_audio_buffer_ RTC_GUARDED_BY(crit_sect_);
CallStatistics call_stats_ RTC_GUARDED_BY(crit_sect_);
const std::unique_ptr<NetEq> neteq_; // NetEq is thread-safe; no lock needed.
Clock* const clock_;
bool resampled_last_output_frame_ RTC_GUARDED_BY(crit_sect_);
};
} // namespace acm2
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_ACM2_ACM_RECEIVER_H_

View File

@ -0,0 +1,464 @@
/*
* 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 <algorithm> // std::min
#include <memory>
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "modules/audio_coding/codecs/cng/audio_encoder_cng.h"
#include "modules/audio_coding/include/audio_coding_module.h"
#include "modules/audio_coding/neteq/tools/rtp_generator.h"
#include "modules/include/module_common_types.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/file_utils.h"
namespace webrtc {
namespace acm2 {
class AcmReceiverTestOldApi : public AudioPacketizationCallback,
public ::testing::Test {
protected:
AcmReceiverTestOldApi()
: timestamp_(0),
packet_sent_(false),
last_packet_send_timestamp_(timestamp_),
last_frame_type_(AudioFrameType::kEmptyFrame) {
config_.decoder_factory = decoder_factory_;
}
~AcmReceiverTestOldApi() {}
void SetUp() override {
acm_.reset(AudioCodingModule::Create(config_));
receiver_.reset(new AcmReceiver(config_));
ASSERT_TRUE(receiver_.get() != NULL);
ASSERT_TRUE(acm_.get() != NULL);
acm_->InitializeReceiver();
acm_->RegisterTransportCallback(this);
rtp_header_.sequenceNumber = 0;
rtp_header_.timestamp = 0;
rtp_header_.markerBit = false;
rtp_header_.ssrc = 0x12345678; // Arbitrary.
rtp_header_.numCSRCs = 0;
rtp_header_.payloadType = 0;
}
void TearDown() override {}
AudioCodecInfo SetEncoder(int payload_type,
const SdpAudioFormat& format,
const std::map<int, int> cng_payload_types = {}) {
// Create the speech encoder.
AudioCodecInfo info = encoder_factory_->QueryAudioEncoder(format).value();
std::unique_ptr<AudioEncoder> enc =
encoder_factory_->MakeAudioEncoder(payload_type, format, absl::nullopt);
// If we have a compatible CN specification, stack a CNG on top.
auto it = cng_payload_types.find(info.sample_rate_hz);
if (it != cng_payload_types.end()) {
AudioEncoderCngConfig config;
config.speech_encoder = std::move(enc);
config.num_channels = 1;
config.payload_type = it->second;
config.vad_mode = Vad::kVadNormal;
enc = CreateComfortNoiseEncoder(std::move(config));
}
// Actually start using the new encoder.
acm_->SetEncoder(std::move(enc));
return info;
}
int InsertOnePacketOfSilence(const AudioCodecInfo& info) {
// Frame setup according to the codec.
AudioFrame frame;
frame.sample_rate_hz_ = info.sample_rate_hz;
frame.samples_per_channel_ = info.sample_rate_hz / 100; // 10 ms.
frame.num_channels_ = info.num_channels;
frame.Mute();
packet_sent_ = false;
last_packet_send_timestamp_ = timestamp_;
int num_10ms_frames = 0;
while (!packet_sent_) {
frame.timestamp_ = timestamp_;
timestamp_ += rtc::checked_cast<uint32_t>(frame.samples_per_channel_);
EXPECT_GE(acm_->Add10MsData(frame), 0);
++num_10ms_frames;
}
return num_10ms_frames;
}
int 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) override {
if (frame_type == AudioFrameType::kEmptyFrame)
return 0;
rtp_header_.payloadType = payload_type;
rtp_header_.timestamp = timestamp;
int ret_val = receiver_->InsertPacket(
rtp_header_,
rtc::ArrayView<const uint8_t>(payload_data, payload_len_bytes));
if (ret_val < 0) {
assert(false);
return -1;
}
rtp_header_.sequenceNumber++;
packet_sent_ = true;
last_frame_type_ = frame_type;
return 0;
}
const rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_ =
CreateBuiltinAudioEncoderFactory();
const rtc::scoped_refptr<AudioDecoderFactory> decoder_factory_ =
CreateBuiltinAudioDecoderFactory();
AudioCodingModule::Config config_;
std::unique_ptr<AcmReceiver> receiver_;
std::unique_ptr<AudioCodingModule> acm_;
RTPHeader rtp_header_;
uint32_t timestamp_;
bool packet_sent_; // Set when SendData is called reset when inserting audio.
uint32_t last_packet_send_timestamp_;
AudioFrameType last_frame_type_;
};
#if defined(WEBRTC_ANDROID)
#define MAYBE_SampleRate DISABLED_SampleRate
#else
#define MAYBE_SampleRate SampleRate
#endif
TEST_F(AcmReceiverTestOldApi, MAYBE_SampleRate) {
const std::map<int, SdpAudioFormat> codecs = {{0, {"ISAC", 16000, 1}},
{1, {"ISAC", 32000, 1}}};
receiver_->SetCodecs(codecs);
constexpr int kOutSampleRateHz = 8000; // Different than codec sample rate.
for (size_t i = 0; i < codecs.size(); ++i) {
const int payload_type = rtc::checked_cast<int>(i);
const int num_10ms_frames =
InsertOnePacketOfSilence(SetEncoder(payload_type, codecs.at(i)));
for (int k = 0; k < num_10ms_frames; ++k) {
AudioFrame frame;
bool muted;
EXPECT_EQ(0, receiver_->GetAudio(kOutSampleRateHz, &frame, &muted));
}
EXPECT_EQ(encoder_factory_->QueryAudioEncoder(codecs.at(i))->sample_rate_hz,
receiver_->last_output_sample_rate_hz());
}
}
class AcmReceiverTestFaxModeOldApi : public AcmReceiverTestOldApi {
protected:
AcmReceiverTestFaxModeOldApi() {
config_.neteq_config.for_test_no_time_stretching = true;
}
void RunVerifyAudioFrame(const SdpAudioFormat& codec) {
// 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_TRUE(config_.neteq_config.for_test_no_time_stretching);
constexpr int payload_type = 17;
receiver_->SetCodecs({{payload_type, codec}});
const AudioCodecInfo info = SetEncoder(payload_type, codec);
const int output_sample_rate_hz = info.sample_rate_hz;
const size_t output_channels = info.num_channels;
const size_t samples_per_ms = rtc::checked_cast<size_t>(
rtc::CheckedDivExact(output_sample_rate_hz, 1000));
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) {
const int num_10ms_frames = InsertOnePacketOfSilence(info);
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<uint32_t>(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({"PCMU", 8000, 1});
}
#if defined(WEBRTC_ANDROID)
#define MAYBE_VerifyAudioFrameISAC DISABLED_VerifyAudioFrameISAC
#else
#define MAYBE_VerifyAudioFrameISAC VerifyAudioFrameISAC
#endif
TEST_F(AcmReceiverTestFaxModeOldApi, MAYBE_VerifyAudioFrameISAC) {
RunVerifyAudioFrame({"ISAC", 16000, 1});
}
#if defined(WEBRTC_ANDROID)
#define MAYBE_VerifyAudioFrameOpus DISABLED_VerifyAudioFrameOpus
#else
#define MAYBE_VerifyAudioFrameOpus VerifyAudioFrameOpus
#endif
TEST_F(AcmReceiverTestFaxModeOldApi, MAYBE_VerifyAudioFrameOpus) {
RunVerifyAudioFrame({"opus", 48000, 2});
}
#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);
constexpr int payload_type = 34;
const SdpAudioFormat codec = {"L16", 16000, 1};
const AudioCodecInfo info = SetEncoder(payload_type, codec);
receiver_->SetCodecs({{payload_type, codec}});
constexpr int kNumPackets = 5;
AudioFrame frame;
for (int n = 0; n < kNumPackets; ++n) {
const int num_10ms_frames = InsertOnePacketOfSilence(info);
for (int k = 0; k < num_10ms_frames; ++k) {
bool muted;
ASSERT_EQ(0, receiver_->GetAudio(info.sample_rate_hz, &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);
constexpr int payload_type = 34;
const SdpAudioFormat codec = {"L16", 16000, 1};
const AudioCodecInfo info = SetEncoder(payload_type, codec);
auto const value = encoder_factory_->QueryAudioEncoder(codec);
ASSERT_TRUE(value.has_value());
receiver_->SetCodecs({{payload_type, codec}});
const int kNumPackets = 5;
AudioFrame frame;
for (int n = 0; n < kNumPackets; ++n) {
const int num_10ms_frames = InsertOnePacketOfSilence(info);
for (int k = 0; k < num_10ms_frames; ++k) {
bool muted;
ASSERT_EQ(0, receiver_->GetAudio(info.sample_rate_hz, &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 std::map<int, SdpAudioFormat> codecs = {{0, {"ISAC", 16000, 1}},
{1, {"PCMA", 8000, 1}},
{2, {"ISAC", 32000, 1}},
{3, {"L16", 32000, 1}}};
const std::map<int, int> cng_payload_types = {
{8000, 100}, {16000, 101}, {32000, 102}};
{
std::map<int, SdpAudioFormat> receive_codecs = codecs;
for (const auto& cng_type : cng_payload_types) {
receive_codecs.emplace(std::make_pair(
cng_type.second, SdpAudioFormat("CN", cng_type.first, 1)));
}
receiver_->SetCodecs(receive_codecs);
}
// No audio payload is received.
EXPECT_EQ(absl::nullopt, receiver_->LastDecoder());
// Start with sending DTX.
packet_sent_ = false;
InsertOnePacketOfSilence(
SetEncoder(0, codecs.at(0), cng_payload_types)); // Enough to test
// with one codec.
ASSERT_TRUE(packet_sent_);
EXPECT_EQ(AudioFrameType::kAudioFrameCN, last_frame_type_);
// Has received, only, DTX. Last Audio codec is undefined.
EXPECT_EQ(absl::nullopt, receiver_->LastDecoder());
EXPECT_EQ(absl::nullopt, receiver_->last_packet_sample_rate_hz());
for (size_t i = 0; i < codecs.size(); ++i) {
// Set DTX off to send audio payload.
packet_sent_ = false;
const int payload_type = rtc::checked_cast<int>(i);
const AudioCodecInfo info_without_cng =
SetEncoder(payload_type, codecs.at(i));
InsertOnePacketOfSilence(info_without_cng);
// Sanity check if Actually an audio payload received, and it should be
// of type "speech."
ASSERT_TRUE(packet_sent_);
ASSERT_EQ(AudioFrameType::kAudioFrameSpeech, last_frame_type_);
EXPECT_EQ(info_without_cng.sample_rate_hz,
receiver_->last_packet_sample_rate_hz());
// Set VAD on to send DTX. Then check if the "Last Audio codec" returns
// the expected codec. Encode repeatedly until a DTX is sent.
const AudioCodecInfo info_with_cng =
SetEncoder(payload_type, codecs.at(i), cng_payload_types);
while (last_frame_type_ != AudioFrameType::kAudioFrameCN) {
packet_sent_ = false;
InsertOnePacketOfSilence(info_with_cng);
ASSERT_TRUE(packet_sent_);
}
EXPECT_EQ(info_with_cng.sample_rate_hz,
receiver_->last_packet_sample_rate_hz());
EXPECT_EQ(codecs.at(i), receiver_->LastDecoder()->second);
}
}
#endif
// Check if the statistics are initialized correctly. Before any call to ACM
// all fields have to be zero.
#if defined(WEBRTC_ANDROID)
#define MAYBE_InitializedToZero DISABLED_InitializedToZero
#else
#define MAYBE_InitializedToZero InitializedToZero
#endif
TEST_F(AcmReceiverTestOldApi, MAYBE_InitializedToZero) {
AudioDecodingCallStats stats;
receiver_->GetDecodingCallStatistics(&stats);
EXPECT_EQ(0, stats.calls_to_neteq);
EXPECT_EQ(0, stats.calls_to_silence_generator);
EXPECT_EQ(0, stats.decoded_normal);
EXPECT_EQ(0, stats.decoded_cng);
EXPECT_EQ(0, stats.decoded_neteq_plc);
EXPECT_EQ(0, stats.decoded_plc_cng);
EXPECT_EQ(0, stats.decoded_muted_output);
}
// Insert some packets and pull audio. Check statistics are valid. Then,
// simulate packet loss and check if PLC and PLC-to-CNG statistics are
// correctly updated.
#if defined(WEBRTC_ANDROID)
#define MAYBE_NetEqCalls DISABLED_NetEqCalls
#else
#define MAYBE_NetEqCalls NetEqCalls
#endif
TEST_F(AcmReceiverTestOldApi, MAYBE_NetEqCalls) {
AudioDecodingCallStats stats;
const int kNumNormalCalls = 10;
const int kSampleRateHz = 16000;
const int kNumSamples10ms = kSampleRateHz / 100;
const int kFrameSizeMs = 10; // Multiple of 10.
const int kFrameSizeSamples = kFrameSizeMs / 10 * kNumSamples10ms;
const int kPayloadSizeBytes = kFrameSizeSamples * sizeof(int16_t);
const uint8_t kPayloadType = 111;
RTPHeader rtp_header;
AudioFrame audio_frame;
bool muted;
receiver_->SetCodecs(
{{kPayloadType, SdpAudioFormat("L16", kSampleRateHz, 1)}});
rtp_header.sequenceNumber = 0xABCD;
rtp_header.timestamp = 0xABCDEF01;
rtp_header.payloadType = kPayloadType;
rtp_header.markerBit = false;
rtp_header.ssrc = 0x1234;
rtp_header.numCSRCs = 0;
rtp_header.payload_type_frequency = kSampleRateHz;
for (int num_calls = 0; num_calls < kNumNormalCalls; ++num_calls) {
const uint8_t kPayload[kPayloadSizeBytes] = {0};
ASSERT_EQ(0, receiver_->InsertPacket(rtp_header, kPayload));
++rtp_header.sequenceNumber;
rtp_header.timestamp += kFrameSizeSamples;
ASSERT_EQ(0, receiver_->GetAudio(-1, &audio_frame, &muted));
EXPECT_FALSE(muted);
}
receiver_->GetDecodingCallStatistics(&stats);
EXPECT_EQ(kNumNormalCalls, stats.calls_to_neteq);
EXPECT_EQ(0, stats.calls_to_silence_generator);
EXPECT_EQ(kNumNormalCalls, stats.decoded_normal);
EXPECT_EQ(0, stats.decoded_cng);
EXPECT_EQ(0, stats.decoded_neteq_plc);
EXPECT_EQ(0, stats.decoded_plc_cng);
EXPECT_EQ(0, stats.decoded_muted_output);
const int kNumPlc = 3;
const int kNumPlcCng = 5;
// Simulate packet-loss. NetEq first performs PLC then PLC fades to CNG.
for (int n = 0; n < kNumPlc + kNumPlcCng; ++n) {
ASSERT_EQ(0, receiver_->GetAudio(-1, &audio_frame, &muted));
EXPECT_FALSE(muted);
}
receiver_->GetDecodingCallStatistics(&stats);
EXPECT_EQ(kNumNormalCalls + kNumPlc + kNumPlcCng, stats.calls_to_neteq);
EXPECT_EQ(0, stats.calls_to_silence_generator);
EXPECT_EQ(kNumNormalCalls, stats.decoded_normal);
EXPECT_EQ(0, stats.decoded_cng);
EXPECT_EQ(kNumPlc, stats.decoded_neteq_plc);
EXPECT_EQ(kNumPlcCng, stats.decoded_plc_cng);
EXPECT_EQ(0, stats.decoded_muted_output);
// TODO(henrik.lundin) Add a test with muted state enabled.
}
} // namespace acm2
} // namespace webrtc

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) 2019 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_remixing.h"
#include "rtc_base/checks.h"
namespace webrtc {
void DownMixFrame(const AudioFrame& input, rtc::ArrayView<int16_t> output) {
RTC_DCHECK_EQ(input.num_channels_, 2);
RTC_DCHECK_EQ(output.size(), input.samples_per_channel_);
if (input.muted()) {
std::fill(output.begin(), output.begin() + input.samples_per_channel_, 0);
} else {
const int16_t* const input_data = input.data();
for (size_t n = 0; n < input.samples_per_channel_; ++n) {
output[n] = rtc::dchecked_cast<int16_t>(
(int32_t{input_data[2 * n]} + int32_t{input_data[2 * n + 1]}) >> 1);
}
}
}
void ReMixFrame(const AudioFrame& input,
size_t num_output_channels,
std::vector<int16_t>* output) {
const size_t output_size = num_output_channels * input.samples_per_channel_;
RTC_DCHECK(!(input.num_channels_ == 0 && num_output_channels > 0 &&
input.samples_per_channel_ > 0));
if (output->size() != output_size) {
output->resize(output_size);
}
// For muted frames, fill the frame with zeros.
if (input.muted()) {
std::fill(output->begin(), output->end(), 0);
return;
}
// Ensure that the special case of zero input channels is handled correctly
// (zero samples per channel is already handled correctly in the code below).
if (input.num_channels_ == 0) {
return;
}
const int16_t* const input_data = input.data();
size_t out_index = 0;
// When upmixing is needed and the input is mono copy the left channel
// into the left and right channels, and set any remaining channels to zero.
if (input.num_channels_ == 1 && input.num_channels_ < num_output_channels) {
for (size_t k = 0; k < input.samples_per_channel_; ++k) {
(*output)[out_index++] = input_data[k];
(*output)[out_index++] = input_data[k];
for (size_t j = 2; j < num_output_channels; ++j) {
(*output)[out_index++] = 0;
}
RTC_DCHECK_EQ(out_index, (k + 1) * num_output_channels);
}
RTC_DCHECK_EQ(out_index, input.samples_per_channel_ * num_output_channels);
return;
}
size_t in_index = 0;
// When upmixing is needed and the output is surround, copy the available
// channels directly, and set the remaining channels to zero.
if (input.num_channels_ < num_output_channels) {
for (size_t k = 0; k < input.samples_per_channel_; ++k) {
for (size_t j = 0; j < input.num_channels_; ++j) {
(*output)[out_index++] = input_data[in_index++];
}
for (size_t j = input.num_channels_; j < num_output_channels; ++j) {
(*output)[out_index++] = 0;
}
RTC_DCHECK_EQ(in_index, (k + 1) * input.num_channels_);
RTC_DCHECK_EQ(out_index, (k + 1) * num_output_channels);
}
RTC_DCHECK_EQ(in_index, input.samples_per_channel_ * input.num_channels_);
RTC_DCHECK_EQ(out_index, input.samples_per_channel_ * num_output_channels);
return;
}
// When downmixing is needed, and the input is stereo, average the channels.
if (input.num_channels_ == 2) {
for (size_t n = 0; n < input.samples_per_channel_; ++n) {
(*output)[n] = rtc::dchecked_cast<int16_t>(
(int32_t{input_data[2 * n]} + int32_t{input_data[2 * n + 1]}) >> 1);
}
return;
}
// When downmixing is needed, and the input is multichannel, drop the surplus
// channels.
const size_t num_channels_to_drop = input.num_channels_ - num_output_channels;
for (size_t k = 0; k < input.samples_per_channel_; ++k) {
for (size_t j = 0; j < num_output_channels; ++j) {
(*output)[out_index++] = input_data[in_index++];
}
in_index += num_channels_to_drop;
}
}
} // namespace webrtc

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2019 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.
*/
#ifndef MODULES_AUDIO_CODING_ACM2_ACM_REMIXING_H_
#define MODULES_AUDIO_CODING_ACM2_ACM_REMIXING_H_
#include <vector>
#include "api/audio/audio_frame.h"
namespace webrtc {
// Stereo-to-mono downmixing. The length of the output must equal to the number
// of samples per channel in the input.
void DownMixFrame(const AudioFrame& input, rtc::ArrayView<int16_t> output);
// Remixes the interleaved input frame to an interleaved output data vector. The
// remixed data replaces the data in the output vector which is resized if
// needed. The remixing supports any combination of input and output channels,
// as well as any number of samples per channel.
void ReMixFrame(const AudioFrame& input,
size_t num_output_channels,
std::vector<int16_t>* output);
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_ACM2_ACM_REMIXING_H_

View File

@ -0,0 +1,191 @@
/*
* 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_remixing.h"
#include <vector>
#include "api/audio/audio_frame.h"
#include "system_wrappers/include/clock.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"
using ::testing::AllOf;
using ::testing::Each;
using ::testing::ElementsAreArray;
using ::testing::SizeIs;
namespace webrtc {
TEST(AcmRemixing, DownMixFrame) {
std::vector<int16_t> out(480, 0);
AudioFrame in;
in.num_channels_ = 2;
in.samples_per_channel_ = 480;
int16_t* const in_data = in.mutable_data();
for (size_t k = 0; k < in.samples_per_channel_; ++k) {
in_data[2 * k] = 2;
in_data[2 * k + 1] = 0;
}
DownMixFrame(in, out);
EXPECT_THAT(out, AllOf(SizeIs(480), Each(1)));
}
TEST(AcmRemixing, DownMixMutedFrame) {
std::vector<int16_t> out(480, 0);
AudioFrame in;
in.num_channels_ = 2;
in.samples_per_channel_ = 480;
int16_t* const in_data = in.mutable_data();
for (size_t k = 0; k < in.samples_per_channel_; ++k) {
in_data[2 * k] = 2;
in_data[2 * k + 1] = 0;
}
in.Mute();
DownMixFrame(in, out);
EXPECT_THAT(out, AllOf(SizeIs(480), Each(0)));
}
TEST(AcmRemixing, RemixMutedStereoFrameTo6Channels) {
std::vector<int16_t> out(480, 0);
AudioFrame in;
in.num_channels_ = 2;
in.samples_per_channel_ = 480;
int16_t* const in_data = in.mutable_data();
for (size_t k = 0; k < in.samples_per_channel_; ++k) {
in_data[2 * k] = 1;
in_data[2 * k + 1] = 2;
}
in.Mute();
ReMixFrame(in, 6, &out);
EXPECT_EQ(6 * 480u, out.size());
EXPECT_THAT(out, AllOf(SizeIs(in.samples_per_channel_ * 6), Each(0)));
}
TEST(AcmRemixing, RemixStereoFrameTo6Channels) {
std::vector<int16_t> out(480, 0);
AudioFrame in;
in.num_channels_ = 2;
in.samples_per_channel_ = 480;
int16_t* const in_data = in.mutable_data();
for (size_t k = 0; k < in.samples_per_channel_; ++k) {
in_data[2 * k] = 1;
in_data[2 * k + 1] = 2;
}
ReMixFrame(in, 6, &out);
EXPECT_EQ(6 * 480u, out.size());
std::vector<int16_t> expected_output(in.samples_per_channel_ * 6);
for (size_t k = 0; k < in.samples_per_channel_; ++k) {
expected_output[6 * k] = 1;
expected_output[6 * k + 1] = 2;
}
EXPECT_THAT(out, ElementsAreArray(expected_output));
}
TEST(AcmRemixing, RemixMonoFrameTo6Channels) {
std::vector<int16_t> out(480, 0);
AudioFrame in;
in.num_channels_ = 1;
in.samples_per_channel_ = 480;
int16_t* const in_data = in.mutable_data();
for (size_t k = 0; k < in.samples_per_channel_; ++k) {
in_data[k] = 1;
}
ReMixFrame(in, 6, &out);
EXPECT_EQ(6 * 480u, out.size());
std::vector<int16_t> expected_output(in.samples_per_channel_ * 6, 0);
for (size_t k = 0; k < in.samples_per_channel_; ++k) {
expected_output[6 * k] = 1;
expected_output[6 * k + 1] = 1;
}
EXPECT_THAT(out, ElementsAreArray(expected_output));
}
TEST(AcmRemixing, RemixStereoFrameToMono) {
std::vector<int16_t> out(480, 0);
AudioFrame in;
in.num_channels_ = 2;
in.samples_per_channel_ = 480;
int16_t* const in_data = in.mutable_data();
for (size_t k = 0; k < in.samples_per_channel_; ++k) {
in_data[2 * k] = 2;
in_data[2 * k + 1] = 0;
}
ReMixFrame(in, 1, &out);
EXPECT_EQ(480u, out.size());
EXPECT_THAT(out, AllOf(SizeIs(in.samples_per_channel_), Each(1)));
}
TEST(AcmRemixing, RemixMonoFrameToStereo) {
std::vector<int16_t> out(480, 0);
AudioFrame in;
in.num_channels_ = 1;
in.samples_per_channel_ = 480;
int16_t* const in_data = in.mutable_data();
for (size_t k = 0; k < in.samples_per_channel_; ++k) {
in_data[k] = 1;
}
ReMixFrame(in, 2, &out);
EXPECT_EQ(960u, out.size());
EXPECT_THAT(out, AllOf(SizeIs(2 * in.samples_per_channel_), Each(1)));
}
TEST(AcmRemixing, Remix3ChannelFrameToStereo) {
std::vector<int16_t> out(480, 0);
AudioFrame in;
in.num_channels_ = 3;
in.samples_per_channel_ = 480;
int16_t* const in_data = in.mutable_data();
for (size_t k = 0; k < in.samples_per_channel_; ++k) {
for (size_t j = 0; j < 3; ++j) {
in_data[3 * k + j] = j;
}
}
ReMixFrame(in, 2, &out);
EXPECT_EQ(2 * 480u, out.size());
std::vector<int16_t> expected_output(in.samples_per_channel_ * 2);
for (size_t k = 0; k < in.samples_per_channel_; ++k) {
for (size_t j = 0; j < 2; ++j) {
expected_output[2 * k + j] = static_cast<int>(j);
}
}
EXPECT_THAT(out, ElementsAreArray(expected_output));
}
} // namespace webrtc

View File

@ -0,0 +1,62 @@
/*
* 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/acm2/acm_resampler.h"
#include <assert.h>
#include <string.h>
#include "rtc_base/logging.h"
namespace webrtc {
namespace acm2 {
ACMResampler::ACMResampler() {}
ACMResampler::~ACMResampler() {}
int ACMResampler::Resample10Msec(const int16_t* in_audio,
int in_freq_hz,
int out_freq_hz,
size_t num_audio_channels,
size_t out_capacity_samples,
int16_t* out_audio) {
size_t in_length = in_freq_hz * num_audio_channels / 100;
if (in_freq_hz == out_freq_hz) {
if (out_capacity_samples < in_length) {
assert(false);
return -1;
}
memcpy(out_audio, in_audio, in_length * sizeof(int16_t));
return static_cast<int>(in_length / num_audio_channels);
}
if (resampler_.InitializeIfNeeded(in_freq_hz, out_freq_hz,
num_audio_channels) != 0) {
RTC_LOG(LS_ERROR) << "InitializeIfNeeded(" << in_freq_hz << ", "
<< out_freq_hz << ", " << num_audio_channels
<< ") failed.";
return -1;
}
int out_length =
resampler_.Resample(in_audio, in_length, out_audio, out_capacity_samples);
if (out_length == -1) {
RTC_LOG(LS_ERROR) << "Resample(" << in_audio << ", " << in_length << ", "
<< out_audio << ", " << out_capacity_samples
<< ") failed.";
return -1;
}
return static_cast<int>(out_length / num_audio_channels);
}
} // namespace acm2
} // namespace webrtc

View File

@ -0,0 +1,41 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_ACM2_ACM_RESAMPLER_H_
#define MODULES_AUDIO_CODING_ACM2_ACM_RESAMPLER_H_
#include <stddef.h>
#include <stdint.h>
#include "common_audio/resampler/include/push_resampler.h"
namespace webrtc {
namespace acm2 {
class ACMResampler {
public:
ACMResampler();
~ACMResampler();
int Resample10Msec(const int16_t* in_audio,
int in_freq_hz,
int out_freq_hz,
size_t num_audio_channels,
size_t out_capacity_samples,
int16_t* out_audio);
private:
PushResampler<int16_t> resampler_;
};
} // namespace acm2
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_ACM2_ACM_RESAMPLER_H_

View File

@ -0,0 +1,173 @@
/*
* Copyright (c) 2014 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_send_test.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "absl/strings/match.h"
#include "api/audio_codecs/audio_encoder.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "modules/audio_coding/include/audio_coding_module.h"
#include "modules/audio_coding/neteq/tools/input_audio_file.h"
#include "modules/audio_coding/neteq/tools/packet.h"
#include "rtc_base/checks.h"
#include "rtc_base/string_encode.h"
#include "test/gtest.h"
namespace webrtc {
namespace test {
AcmSendTestOldApi::AcmSendTestOldApi(InputAudioFile* audio_source,
int source_rate_hz,
int test_duration_ms)
: clock_(0),
acm_(webrtc::AudioCodingModule::Create([this] {
AudioCodingModule::Config config;
config.clock = &clock_;
config.decoder_factory = CreateBuiltinAudioDecoderFactory();
return config;
}())),
audio_source_(audio_source),
source_rate_hz_(source_rate_hz),
input_block_size_samples_(
static_cast<size_t>(source_rate_hz_ * kBlockSizeMs / 1000)),
codec_registered_(false),
test_duration_ms_(test_duration_ms),
frame_type_(AudioFrameType::kAudioFrameSpeech),
payload_type_(0),
timestamp_(0),
sequence_number_(0) {
input_frame_.sample_rate_hz_ = source_rate_hz_;
input_frame_.num_channels_ = 1;
input_frame_.samples_per_channel_ = input_block_size_samples_;
assert(input_block_size_samples_ * input_frame_.num_channels_ <=
AudioFrame::kMaxDataSizeSamples);
acm_->RegisterTransportCallback(this);
}
AcmSendTestOldApi::~AcmSendTestOldApi() = default;
bool AcmSendTestOldApi::RegisterCodec(const char* payload_name,
int clockrate_hz,
int num_channels,
int payload_type,
int frame_size_samples) {
SdpAudioFormat format(payload_name, clockrate_hz, num_channels);
if (absl::EqualsIgnoreCase(payload_name, "g722")) {
RTC_CHECK_EQ(16000, clockrate_hz);
format.clockrate_hz = 8000;
} else if (absl::EqualsIgnoreCase(payload_name, "opus")) {
RTC_CHECK(num_channels == 1 || num_channels == 2);
if (num_channels == 2) {
format.parameters["stereo"] = "1";
}
format.num_channels = 2;
}
format.parameters["ptime"] = rtc::ToString(rtc::CheckedDivExact(
frame_size_samples, rtc::CheckedDivExact(clockrate_hz, 1000)));
auto factory = CreateBuiltinAudioEncoderFactory();
acm_->SetEncoder(
factory->MakeAudioEncoder(payload_type, format, absl::nullopt));
codec_registered_ = true;
input_frame_.num_channels_ = num_channels;
assert(input_block_size_samples_ * input_frame_.num_channels_ <=
AudioFrame::kMaxDataSizeSamples);
return codec_registered_;
}
void AcmSendTestOldApi::RegisterExternalCodec(
std::unique_ptr<AudioEncoder> external_speech_encoder) {
input_frame_.num_channels_ = external_speech_encoder->NumChannels();
acm_->SetEncoder(std::move(external_speech_encoder));
assert(input_block_size_samples_ * input_frame_.num_channels_ <=
AudioFrame::kMaxDataSizeSamples);
codec_registered_ = true;
}
std::unique_ptr<Packet> AcmSendTestOldApi::NextPacket() {
assert(codec_registered_);
if (filter_.test(static_cast<size_t>(payload_type_))) {
// This payload type should be filtered out. Since the payload type is the
// same throughout the whole test run, no packet at all will be delivered.
// We can just as well signal that the test is over by returning NULL.
return nullptr;
}
// Insert audio and process until one packet is produced.
while (clock_.TimeInMilliseconds() < test_duration_ms_) {
clock_.AdvanceTimeMilliseconds(kBlockSizeMs);
RTC_CHECK(audio_source_->Read(
input_block_size_samples_ * input_frame_.num_channels_,
input_frame_.mutable_data()));
data_to_send_ = false;
RTC_CHECK_GE(acm_->Add10MsData(input_frame_), 0);
input_frame_.timestamp_ += static_cast<uint32_t>(input_block_size_samples_);
if (data_to_send_) {
// Encoded packet received.
return CreatePacket();
}
}
// Test ended.
return nullptr;
}
// This method receives the callback from ACM when a new packet is produced.
int32_t AcmSendTestOldApi::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) {
// Store the packet locally.
frame_type_ = frame_type;
payload_type_ = payload_type;
timestamp_ = timestamp;
last_payload_vec_.assign(payload_data, payload_data + payload_len_bytes);
assert(last_payload_vec_.size() == payload_len_bytes);
data_to_send_ = true;
return 0;
}
std::unique_ptr<Packet> AcmSendTestOldApi::CreatePacket() {
const size_t kRtpHeaderSize = 12;
size_t allocated_bytes = last_payload_vec_.size() + kRtpHeaderSize;
uint8_t* packet_memory = new uint8_t[allocated_bytes];
// Populate the header bytes.
packet_memory[0] = 0x80;
packet_memory[1] = static_cast<uint8_t>(payload_type_);
packet_memory[2] = (sequence_number_ >> 8) & 0xFF;
packet_memory[3] = (sequence_number_)&0xFF;
packet_memory[4] = (timestamp_ >> 24) & 0xFF;
packet_memory[5] = (timestamp_ >> 16) & 0xFF;
packet_memory[6] = (timestamp_ >> 8) & 0xFF;
packet_memory[7] = timestamp_ & 0xFF;
// Set SSRC to 0x12345678.
packet_memory[8] = 0x12;
packet_memory[9] = 0x34;
packet_memory[10] = 0x56;
packet_memory[11] = 0x78;
++sequence_number_;
// Copy the payload data.
memcpy(packet_memory + kRtpHeaderSize, &last_payload_vec_[0],
last_payload_vec_.size());
std::unique_ptr<Packet> packet(
new Packet(packet_memory, allocated_bytes, clock_.TimeInMilliseconds()));
RTC_DCHECK(packet);
RTC_DCHECK(packet->valid_header());
return packet;
}
} // namespace test
} // namespace webrtc

View File

@ -0,0 +1,90 @@
/*
* Copyright (c) 2014 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.
*/
#ifndef MODULES_AUDIO_CODING_ACM2_ACM_SEND_TEST_H_
#define MODULES_AUDIO_CODING_ACM2_ACM_SEND_TEST_H_
#include <memory>
#include <vector>
#include "api/audio/audio_frame.h"
#include "modules/audio_coding/include/audio_coding_module.h"
#include "modules/audio_coding/neteq/tools/packet_source.h"
#include "rtc_base/constructor_magic.h"
#include "system_wrappers/include/clock.h"
namespace webrtc {
class AudioEncoder;
namespace test {
class InputAudioFile;
class Packet;
class AcmSendTestOldApi : public AudioPacketizationCallback,
public PacketSource {
public:
AcmSendTestOldApi(InputAudioFile* audio_source,
int source_rate_hz,
int test_duration_ms);
~AcmSendTestOldApi() override;
// Registers the send codec. Returns true on success, false otherwise.
bool RegisterCodec(const char* payload_name,
int sampling_freq_hz,
int channels,
int payload_type,
int frame_size_samples);
// Registers an external send codec.
void RegisterExternalCodec(
std::unique_ptr<AudioEncoder> external_speech_encoder);
// Inherited from PacketSource.
std::unique_ptr<Packet> NextPacket() override;
// Inherited from AudioPacketizationCallback.
int32_t 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) override;
AudioCodingModule* acm() { return acm_.get(); }
private:
static const int kBlockSizeMs = 10;
// Creates a Packet object from the last packet produced by ACM (and received
// through the SendData method as a callback).
std::unique_ptr<Packet> CreatePacket();
SimulatedClock clock_;
std::unique_ptr<AudioCodingModule> acm_;
InputAudioFile* audio_source_;
int source_rate_hz_;
const size_t input_block_size_samples_;
AudioFrame input_frame_;
bool codec_registered_;
int test_duration_ms_;
// The following member variables are set whenever SendData() is called.
AudioFrameType frame_type_;
int payload_type_;
uint32_t timestamp_;
uint16_t sequence_number_;
std::vector<uint8_t> last_payload_vec_;
bool data_to_send_;
RTC_DISALLOW_COPY_AND_ASSIGN(AcmSendTestOldApi);
};
} // namespace test
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_ACM2_ACM_SEND_TEST_H_

View File

@ -0,0 +1,626 @@
/*
* 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/include/audio_coding_module.h"
#include <assert.h>
#include <algorithm>
#include <cstdint>
#include "absl/strings/match.h"
#include "api/array_view.h"
#include "modules/audio_coding/acm2/acm_receiver.h"
#include "modules/audio_coding/acm2/acm_remixing.h"
#include "modules/audio_coding/acm2/acm_resampler.h"
#include "modules/include/module_common_types.h"
#include "modules/include/module_common_types_public.h"
#include "rtc_base/buffer.h"
#include "rtc_base/checks.h"
#include "rtc_base/critical_section.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/thread_annotations.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
namespace {
// Initial size for the buffer in InputBuffer. This matches 6 channels of 10 ms
// 48 kHz data.
constexpr size_t kInitialInputDataBufferSize = 6 * 480;
constexpr int32_t kMaxInputSampleRateHz = 192000;
class AudioCodingModuleImpl final : public AudioCodingModule {
public:
explicit AudioCodingModuleImpl(const AudioCodingModule::Config& config);
~AudioCodingModuleImpl() override;
/////////////////////////////////////////
// Sender
//
void ModifyEncoder(rtc::FunctionView<void(std::unique_ptr<AudioEncoder>*)>
modifier) override;
// Register a transport callback which will be
// called to deliver the encoded buffers.
int RegisterTransportCallback(AudioPacketizationCallback* transport) override;
// Add 10 ms of raw (PCM) audio data to the encoder.
int Add10MsData(const AudioFrame& audio_frame) override;
/////////////////////////////////////////
// (FEC) Forward Error Correction (codec internal)
//
// Set target packet loss rate
int SetPacketLossRate(int loss_rate) override;
/////////////////////////////////////////
// Receiver
//
// Initialize receiver, resets codec database etc.
int InitializeReceiver() override;
void SetReceiveCodecs(const std::map<int, SdpAudioFormat>& codecs) override;
// Incoming packet from network parsed and ready for decode.
int IncomingPacket(const uint8_t* incoming_payload,
const size_t payload_length,
const RTPHeader& rtp_info) override;
// Get 10 milliseconds of raw audio data to play out, and
// automatic resample to the requested frequency if > 0.
int PlayoutData10Ms(int desired_freq_hz,
AudioFrame* audio_frame,
bool* muted) override;
/////////////////////////////////////////
// Statistics
//
int GetNetworkStatistics(NetworkStatistics* statistics) override;
ANAStats GetANAStats() const override;
private:
struct InputData {
InputData() : buffer(kInitialInputDataBufferSize) {}
uint32_t input_timestamp;
const int16_t* audio;
size_t length_per_channel;
size_t audio_channel;
// If a re-mix is required (up or down), this buffer will store a re-mixed
// version of the input.
std::vector<int16_t> buffer;
};
InputData input_data_ RTC_GUARDED_BY(acm_crit_sect_);
// This member class writes values to the named UMA histogram, but only if
// the value has changed since the last time (and always for the first call).
class ChangeLogger {
public:
explicit ChangeLogger(const std::string& histogram_name)
: histogram_name_(histogram_name) {}
// Logs the new value if it is different from the last logged value, or if
// this is the first call.
void MaybeLog(int value);
private:
int last_value_ = 0;
int first_time_ = true;
const std::string histogram_name_;
};
int Add10MsDataInternal(const AudioFrame& audio_frame, InputData* input_data)
RTC_EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_);
// TODO(bugs.webrtc.org/10739): change |absolute_capture_timestamp_ms| to
// int64_t when it always receives a valid value.
int Encode(const InputData& input_data,
absl::optional<int64_t> absolute_capture_timestamp_ms)
RTC_EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_);
int InitializeReceiverSafe() RTC_EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_);
bool HaveValidEncoder(const char* caller_name) const
RTC_EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_);
// Preprocessing of input audio, including resampling and down-mixing if
// required, before pushing audio into encoder's buffer.
//
// in_frame: input audio-frame
// ptr_out: pointer to output audio_frame. If no preprocessing is required
// |ptr_out| will be pointing to |in_frame|, otherwise pointing to
// |preprocess_frame_|.
//
// Return value:
// -1: if encountering an error.
// 0: otherwise.
int PreprocessToAddData(const AudioFrame& in_frame,
const AudioFrame** ptr_out)
RTC_EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_);
// Change required states after starting to receive the codec corresponding
// to |index|.
int UpdateUponReceivingCodec(int index);
rtc::CriticalSection acm_crit_sect_;
rtc::Buffer encode_buffer_ RTC_GUARDED_BY(acm_crit_sect_);
uint32_t expected_codec_ts_ RTC_GUARDED_BY(acm_crit_sect_);
uint32_t expected_in_ts_ RTC_GUARDED_BY(acm_crit_sect_);
acm2::ACMResampler resampler_ RTC_GUARDED_BY(acm_crit_sect_);
acm2::AcmReceiver receiver_; // AcmReceiver has it's own internal lock.
ChangeLogger bitrate_logger_ RTC_GUARDED_BY(acm_crit_sect_);
// Current encoder stack, provided by a call to RegisterEncoder.
std::unique_ptr<AudioEncoder> encoder_stack_ RTC_GUARDED_BY(acm_crit_sect_);
// This is to keep track of CN instances where we can send DTMFs.
uint8_t previous_pltype_ RTC_GUARDED_BY(acm_crit_sect_);
bool receiver_initialized_ RTC_GUARDED_BY(acm_crit_sect_);
AudioFrame preprocess_frame_ RTC_GUARDED_BY(acm_crit_sect_);
bool first_10ms_data_ RTC_GUARDED_BY(acm_crit_sect_);
bool first_frame_ RTC_GUARDED_BY(acm_crit_sect_);
uint32_t last_timestamp_ RTC_GUARDED_BY(acm_crit_sect_);
uint32_t last_rtp_timestamp_ RTC_GUARDED_BY(acm_crit_sect_);
rtc::CriticalSection callback_crit_sect_;
AudioPacketizationCallback* packetization_callback_
RTC_GUARDED_BY(callback_crit_sect_);
int codec_histogram_bins_log_[static_cast<size_t>(
AudioEncoder::CodecType::kMaxLoggedAudioCodecTypes)];
int number_of_consecutive_empty_packets_;
};
// Adds a codec usage sample to the histogram.
void UpdateCodecTypeHistogram(size_t codec_type) {
RTC_HISTOGRAM_ENUMERATION(
"WebRTC.Audio.Encoder.CodecType", static_cast<int>(codec_type),
static_cast<int>(
webrtc::AudioEncoder::CodecType::kMaxLoggedAudioCodecTypes));
}
void AudioCodingModuleImpl::ChangeLogger::MaybeLog(int value) {
if (value != last_value_ || first_time_) {
first_time_ = false;
last_value_ = value;
RTC_HISTOGRAM_COUNTS_SPARSE_100(histogram_name_, value);
}
}
AudioCodingModuleImpl::AudioCodingModuleImpl(
const AudioCodingModule::Config& config)
: expected_codec_ts_(0xD87F3F9F),
expected_in_ts_(0xD87F3F9F),
receiver_(config),
bitrate_logger_("WebRTC.Audio.TargetBitrateInKbps"),
encoder_stack_(nullptr),
previous_pltype_(255),
receiver_initialized_(false),
first_10ms_data_(false),
first_frame_(true),
packetization_callback_(NULL),
codec_histogram_bins_log_(),
number_of_consecutive_empty_packets_(0) {
if (InitializeReceiverSafe() < 0) {
RTC_LOG(LS_ERROR) << "Cannot initialize receiver";
}
RTC_LOG(LS_INFO) << "Created";
}
AudioCodingModuleImpl::~AudioCodingModuleImpl() = default;
int32_t AudioCodingModuleImpl::Encode(
const InputData& input_data,
absl::optional<int64_t> absolute_capture_timestamp_ms) {
// TODO(bugs.webrtc.org/10739): add dcheck that
// |audio_frame.absolute_capture_timestamp_ms()| always has a value.
AudioEncoder::EncodedInfo encoded_info;
uint8_t previous_pltype;
// Check if there is an encoder before.
if (!HaveValidEncoder("Process"))
return -1;
if (!first_frame_) {
RTC_DCHECK(IsNewerTimestamp(input_data.input_timestamp, last_timestamp_))
<< "Time should not move backwards";
}
// Scale the timestamp to the codec's RTP timestamp rate.
uint32_t rtp_timestamp =
first_frame_
? input_data.input_timestamp
: last_rtp_timestamp_ +
rtc::dchecked_cast<uint32_t>(rtc::CheckedDivExact(
int64_t{input_data.input_timestamp - last_timestamp_} *
encoder_stack_->RtpTimestampRateHz(),
int64_t{encoder_stack_->SampleRateHz()}));
last_timestamp_ = input_data.input_timestamp;
last_rtp_timestamp_ = rtp_timestamp;
first_frame_ = false;
// Clear the buffer before reuse - encoded data will get appended.
encode_buffer_.Clear();
encoded_info = encoder_stack_->Encode(
rtp_timestamp,
rtc::ArrayView<const int16_t>(
input_data.audio,
input_data.audio_channel * input_data.length_per_channel),
&encode_buffer_);
bitrate_logger_.MaybeLog(encoder_stack_->GetTargetBitrate() / 1000);
if (encode_buffer_.size() == 0 && !encoded_info.send_even_if_empty) {
// Not enough data.
return 0;
}
previous_pltype = previous_pltype_; // Read it while we have the critsect.
// Log codec type to histogram once every 500 packets.
if (encoded_info.encoded_bytes == 0) {
++number_of_consecutive_empty_packets_;
} else {
size_t codec_type = static_cast<size_t>(encoded_info.encoder_type);
codec_histogram_bins_log_[codec_type] +=
number_of_consecutive_empty_packets_ + 1;
number_of_consecutive_empty_packets_ = 0;
if (codec_histogram_bins_log_[codec_type] >= 500) {
codec_histogram_bins_log_[codec_type] -= 500;
UpdateCodecTypeHistogram(codec_type);
}
}
AudioFrameType frame_type;
if (encode_buffer_.size() == 0 && encoded_info.send_even_if_empty) {
frame_type = AudioFrameType::kEmptyFrame;
encoded_info.payload_type = previous_pltype;
} else {
RTC_DCHECK_GT(encode_buffer_.size(), 0);
frame_type = encoded_info.speech ? AudioFrameType::kAudioFrameSpeech
: AudioFrameType::kAudioFrameCN;
}
{
rtc::CritScope lock(&callback_crit_sect_);
if (packetization_callback_) {
packetization_callback_->SendData(
frame_type, encoded_info.payload_type, encoded_info.encoded_timestamp,
encode_buffer_.data(), encode_buffer_.size(),
absolute_capture_timestamp_ms.value_or(-1));
}
}
previous_pltype_ = encoded_info.payload_type;
return static_cast<int32_t>(encode_buffer_.size());
}
/////////////////////////////////////////
// Sender
//
void AudioCodingModuleImpl::ModifyEncoder(
rtc::FunctionView<void(std::unique_ptr<AudioEncoder>*)> modifier) {
rtc::CritScope lock(&acm_crit_sect_);
modifier(&encoder_stack_);
}
// Register a transport callback which will be called to deliver
// the encoded buffers.
int AudioCodingModuleImpl::RegisterTransportCallback(
AudioPacketizationCallback* transport) {
rtc::CritScope lock(&callback_crit_sect_);
packetization_callback_ = transport;
return 0;
}
// Add 10MS of raw (PCM) audio data to the encoder.
int AudioCodingModuleImpl::Add10MsData(const AudioFrame& audio_frame) {
rtc::CritScope lock(&acm_crit_sect_);
int r = Add10MsDataInternal(audio_frame, &input_data_);
// TODO(bugs.webrtc.org/10739): add dcheck that
// |audio_frame.absolute_capture_timestamp_ms()| always has a value.
return r < 0
? r
: Encode(input_data_, audio_frame.absolute_capture_timestamp_ms());
}
int AudioCodingModuleImpl::Add10MsDataInternal(const AudioFrame& audio_frame,
InputData* input_data) {
if (audio_frame.samples_per_channel_ == 0) {
assert(false);
RTC_LOG(LS_ERROR) << "Cannot Add 10 ms audio, payload length is zero";
return -1;
}
if (audio_frame.sample_rate_hz_ > kMaxInputSampleRateHz) {
assert(false);
RTC_LOG(LS_ERROR) << "Cannot Add 10 ms audio, input frequency not valid";
return -1;
}
// If the length and frequency matches. We currently just support raw PCM.
if (static_cast<size_t>(audio_frame.sample_rate_hz_ / 100) !=
audio_frame.samples_per_channel_) {
RTC_LOG(LS_ERROR)
<< "Cannot Add 10 ms audio, input frequency and length doesn't match";
return -1;
}
if (audio_frame.num_channels_ != 1 && audio_frame.num_channels_ != 2 &&
audio_frame.num_channels_ != 4 && audio_frame.num_channels_ != 6 &&
audio_frame.num_channels_ != 8) {
RTC_LOG(LS_ERROR) << "Cannot Add 10 ms audio, invalid number of channels.";
return -1;
}
// Do we have a codec registered?
if (!HaveValidEncoder("Add10MsData")) {
return -1;
}
const AudioFrame* ptr_frame;
// Perform a resampling, also down-mix if it is required and can be
// performed before resampling (a down mix prior to resampling will take
// place if both primary and secondary encoders are mono and input is in
// stereo).
if (PreprocessToAddData(audio_frame, &ptr_frame) < 0) {
return -1;
}
// Check whether we need an up-mix or down-mix?
const size_t current_num_channels = encoder_stack_->NumChannels();
const bool same_num_channels =
ptr_frame->num_channels_ == current_num_channels;
// TODO(yujo): Skip encode of muted frames.
input_data->input_timestamp = ptr_frame->timestamp_;
input_data->length_per_channel = ptr_frame->samples_per_channel_;
input_data->audio_channel = current_num_channels;
if (!same_num_channels) {
// Remixes the input frame to the output data and in the process resize the
// output data if needed.
ReMixFrame(*ptr_frame, current_num_channels, &input_data->buffer);
// For pushing data to primary, point the |ptr_audio| to correct buffer.
input_data->audio = input_data->buffer.data();
RTC_DCHECK_GE(input_data->buffer.size(),
input_data->length_per_channel * input_data->audio_channel);
} else {
// When adding data to encoders this pointer is pointing to an audio buffer
// with correct number of channels.
input_data->audio = ptr_frame->data();
}
return 0;
}
// Perform a resampling and down-mix if required. We down-mix only if
// encoder is mono and input is stereo. In case of dual-streaming, both
// encoders has to be mono for down-mix to take place.
// |*ptr_out| will point to the pre-processed audio-frame. If no pre-processing
// is required, |*ptr_out| points to |in_frame|.
// TODO(yujo): Make this more efficient for muted frames.
int AudioCodingModuleImpl::PreprocessToAddData(const AudioFrame& in_frame,
const AudioFrame** ptr_out) {
const bool resample =
in_frame.sample_rate_hz_ != encoder_stack_->SampleRateHz();
// This variable is true if primary codec and secondary codec (if exists)
// are both mono and input is stereo.
// TODO(henrik.lundin): This condition should probably be
// in_frame.num_channels_ > encoder_stack_->NumChannels()
const bool down_mix =
in_frame.num_channels_ == 2 && encoder_stack_->NumChannels() == 1;
if (!first_10ms_data_) {
expected_in_ts_ = in_frame.timestamp_;
expected_codec_ts_ = in_frame.timestamp_;
first_10ms_data_ = true;
} else if (in_frame.timestamp_ != expected_in_ts_) {
RTC_LOG(LS_WARNING) << "Unexpected input timestamp: " << in_frame.timestamp_
<< ", expected: " << expected_in_ts_;
expected_codec_ts_ +=
(in_frame.timestamp_ - expected_in_ts_) *
static_cast<uint32_t>(
static_cast<double>(encoder_stack_->SampleRateHz()) /
static_cast<double>(in_frame.sample_rate_hz_));
expected_in_ts_ = in_frame.timestamp_;
}
if (!down_mix && !resample) {
// No pre-processing is required.
if (expected_in_ts_ == expected_codec_ts_) {
// If we've never resampled, we can use the input frame as-is
*ptr_out = &in_frame;
} else {
// Otherwise we'll need to alter the timestamp. Since in_frame is const,
// we'll have to make a copy of it.
preprocess_frame_.CopyFrom(in_frame);
preprocess_frame_.timestamp_ = expected_codec_ts_;
*ptr_out = &preprocess_frame_;
}
expected_in_ts_ += static_cast<uint32_t>(in_frame.samples_per_channel_);
expected_codec_ts_ += static_cast<uint32_t>(in_frame.samples_per_channel_);
return 0;
}
*ptr_out = &preprocess_frame_;
preprocess_frame_.num_channels_ = in_frame.num_channels_;
preprocess_frame_.samples_per_channel_ = in_frame.samples_per_channel_;
std::array<int16_t, AudioFrame::kMaxDataSizeSamples> audio;
const int16_t* src_ptr_audio;
if (down_mix) {
// If a resampling is required, the output of a down-mix is written into a
// local buffer, otherwise, it will be written to the output frame.
int16_t* dest_ptr_audio =
resample ? audio.data() : preprocess_frame_.mutable_data();
RTC_DCHECK_GE(audio.size(), preprocess_frame_.samples_per_channel_);
RTC_DCHECK_GE(audio.size(), in_frame.samples_per_channel_);
DownMixFrame(in_frame,
rtc::ArrayView<int16_t>(
dest_ptr_audio, preprocess_frame_.samples_per_channel_));
preprocess_frame_.num_channels_ = 1;
// Set the input of the resampler to the down-mixed signal.
src_ptr_audio = audio.data();
} else {
// Set the input of the resampler to the original data.
src_ptr_audio = in_frame.data();
}
preprocess_frame_.timestamp_ = expected_codec_ts_;
preprocess_frame_.sample_rate_hz_ = in_frame.sample_rate_hz_;
// If it is required, we have to do a resampling.
if (resample) {
// The result of the resampler is written to output frame.
int16_t* dest_ptr_audio = preprocess_frame_.mutable_data();
int samples_per_channel = resampler_.Resample10Msec(
src_ptr_audio, in_frame.sample_rate_hz_, encoder_stack_->SampleRateHz(),
preprocess_frame_.num_channels_, AudioFrame::kMaxDataSizeSamples,
dest_ptr_audio);
if (samples_per_channel < 0) {
RTC_LOG(LS_ERROR) << "Cannot add 10 ms audio, resampling failed";
return -1;
}
preprocess_frame_.samples_per_channel_ =
static_cast<size_t>(samples_per_channel);
preprocess_frame_.sample_rate_hz_ = encoder_stack_->SampleRateHz();
}
expected_codec_ts_ +=
static_cast<uint32_t>(preprocess_frame_.samples_per_channel_);
expected_in_ts_ += static_cast<uint32_t>(in_frame.samples_per_channel_);
return 0;
}
/////////////////////////////////////////
// (FEC) Forward Error Correction (codec internal)
//
int AudioCodingModuleImpl::SetPacketLossRate(int loss_rate) {
rtc::CritScope lock(&acm_crit_sect_);
if (HaveValidEncoder("SetPacketLossRate")) {
encoder_stack_->OnReceivedUplinkPacketLossFraction(loss_rate / 100.0);
}
return 0;
}
/////////////////////////////////////////
// Receiver
//
int AudioCodingModuleImpl::InitializeReceiver() {
rtc::CritScope lock(&acm_crit_sect_);
return InitializeReceiverSafe();
}
// Initialize receiver, resets codec database etc.
int AudioCodingModuleImpl::InitializeReceiverSafe() {
// If the receiver is already initialized then we want to destroy any
// existing decoders. After a call to this function, we should have a clean
// start-up.
if (receiver_initialized_)
receiver_.RemoveAllCodecs();
receiver_.FlushBuffers();
receiver_initialized_ = true;
return 0;
}
void AudioCodingModuleImpl::SetReceiveCodecs(
const std::map<int, SdpAudioFormat>& codecs) {
rtc::CritScope lock(&acm_crit_sect_);
receiver_.SetCodecs(codecs);
}
// Incoming packet from network parsed and ready for decode.
int AudioCodingModuleImpl::IncomingPacket(const uint8_t* incoming_payload,
const size_t payload_length,
const RTPHeader& rtp_header) {
RTC_DCHECK_EQ(payload_length == 0, incoming_payload == nullptr);
return receiver_.InsertPacket(
rtp_header,
rtc::ArrayView<const uint8_t>(incoming_payload, payload_length));
}
// Get 10 milliseconds of raw audio data to play out.
// Automatic resample to the requested frequency.
int AudioCodingModuleImpl::PlayoutData10Ms(int desired_freq_hz,
AudioFrame* audio_frame,
bool* muted) {
// GetAudio always returns 10 ms, at the requested sample rate.
if (receiver_.GetAudio(desired_freq_hz, audio_frame, muted) != 0) {
RTC_LOG(LS_ERROR) << "PlayoutData failed, RecOut Failed";
return -1;
}
return 0;
}
/////////////////////////////////////////
// Statistics
//
// TODO(turajs) change the return value to void. Also change the corresponding
// NetEq function.
int AudioCodingModuleImpl::GetNetworkStatistics(NetworkStatistics* statistics) {
receiver_.GetNetworkStatistics(statistics);
return 0;
}
bool AudioCodingModuleImpl::HaveValidEncoder(const char* caller_name) const {
if (!encoder_stack_) {
RTC_LOG(LS_ERROR) << caller_name << " failed: No send codec is registered.";
return false;
}
return true;
}
ANAStats AudioCodingModuleImpl::GetANAStats() const {
rtc::CritScope lock(&acm_crit_sect_);
if (encoder_stack_)
return encoder_stack_->GetANAStats();
// If no encoder is set, return default stats.
return ANAStats();
}
} // namespace
AudioCodingModule::Config::Config(
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory)
: neteq_config(),
clock(Clock::GetRealTimeClock()),
decoder_factory(decoder_factory) {
// Post-decode VAD is disabled by default in NetEq, however, Audio
// Conference Mixer relies on VAD decisions and fails without them.
neteq_config.enable_post_decode_vad = true;
}
AudioCodingModule::Config::Config(const Config&) = default;
AudioCodingModule::Config::~Config() = default;
AudioCodingModule* AudioCodingModule::Create(const Config& config) {
return new AudioCodingModuleImpl(config);
}
} // namespace webrtc

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,63 @@
/*
* 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/call_statistics.h"
#include "rtc_base/checks.h"
namespace webrtc {
namespace acm2 {
void CallStatistics::DecodedByNetEq(AudioFrame::SpeechType speech_type,
bool muted) {
++decoding_stat_.calls_to_neteq;
if (muted) {
++decoding_stat_.decoded_muted_output;
}
switch (speech_type) {
case AudioFrame::kNormalSpeech: {
++decoding_stat_.decoded_normal;
break;
}
case AudioFrame::kPLC: {
++decoding_stat_.decoded_neteq_plc;
break;
}
case AudioFrame::kCodecPLC: {
++decoding_stat_.decoded_codec_plc;
break;
}
case AudioFrame::kCNG: {
++decoding_stat_.decoded_cng;
break;
}
case AudioFrame::kPLCCNG: {
++decoding_stat_.decoded_plc_cng;
break;
}
case AudioFrame::kUndefined: {
// If the audio is decoded by NetEq, |kUndefined| is not an option.
RTC_NOTREACHED();
}
}
}
void CallStatistics::DecodedBySilenceGenerator() {
++decoding_stat_.calls_to_silence_generator;
}
const AudioDecodingCallStats& CallStatistics::GetDecodingStatistics() const {
return decoding_stat_;
}
} // namespace acm2
} // namespace webrtc

View File

@ -0,0 +1,64 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_ACM2_CALL_STATISTICS_H_
#define MODULES_AUDIO_CODING_ACM2_CALL_STATISTICS_H_
#include "api/audio/audio_frame.h"
#include "modules/audio_coding/include/audio_coding_module_typedefs.h"
//
// This class is for book keeping of calls to ACM. It is not useful to log API
// calls which are supposed to be called every 10ms, e.g. PlayoutData10Ms(),
// however, it is useful to know the number of such calls in a given time
// interval. The current implementation covers calls to PlayoutData10Ms() with
// detailed accounting of the decoded speech type.
//
// Thread Safety
// =============
// Please note that this class in not thread safe. The class must be protected
// if different APIs are called from different threads.
//
namespace webrtc {
namespace acm2 {
class CallStatistics {
public:
CallStatistics() {}
~CallStatistics() {}
// Call this method to indicate that NetEq engaged in decoding. |speech_type|
// is the audio-type according to NetEq, and |muted| indicates if the decoded
// frame was produced in muted state.
void DecodedByNetEq(AudioFrame::SpeechType speech_type, bool muted);
// Call this method to indicate that a decoding call resulted in generating
// silence, i.e. call to NetEq is bypassed and the output audio is zero.
void DecodedBySilenceGenerator();
// Get statistics for decoding. The statistics include the number of calls to
// NetEq and silence generator, as well as the type of speech pulled of off
// NetEq, c.f. declaration of AudioDecodingCallStats for detailed description.
const AudioDecodingCallStats& GetDecodingStatistics() const;
private:
// Reset the decoding statistics.
void ResetDecodingStatistics();
AudioDecodingCallStats decoding_stat_;
};
} // namespace acm2
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_ACM2_CALL_STATISTICS_H_

View File

@ -0,0 +1,57 @@
/*
* 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/call_statistics.h"
#include "test/gtest.h"
namespace webrtc {
namespace acm2 {
TEST(CallStatisticsTest, InitializedZero) {
CallStatistics call_stats;
AudioDecodingCallStats stats;
stats = call_stats.GetDecodingStatistics();
EXPECT_EQ(0, stats.calls_to_neteq);
EXPECT_EQ(0, stats.calls_to_silence_generator);
EXPECT_EQ(0, stats.decoded_normal);
EXPECT_EQ(0, stats.decoded_cng);
EXPECT_EQ(0, stats.decoded_neteq_plc);
EXPECT_EQ(0, stats.decoded_plc_cng);
EXPECT_EQ(0, stats.decoded_muted_output);
}
TEST(CallStatisticsTest, AllCalls) {
CallStatistics call_stats;
AudioDecodingCallStats stats;
call_stats.DecodedBySilenceGenerator();
call_stats.DecodedByNetEq(AudioFrame::kNormalSpeech, false);
call_stats.DecodedByNetEq(AudioFrame::kPLC, false);
call_stats.DecodedByNetEq(AudioFrame::kCodecPLC, false);
call_stats.DecodedByNetEq(AudioFrame::kPLCCNG, true); // Let this be muted.
call_stats.DecodedByNetEq(AudioFrame::kCNG, false);
stats = call_stats.GetDecodingStatistics();
EXPECT_EQ(5, stats.calls_to_neteq);
EXPECT_EQ(1, stats.calls_to_silence_generator);
EXPECT_EQ(1, stats.decoded_normal);
EXPECT_EQ(1, stats.decoded_cng);
EXPECT_EQ(1, stats.decoded_neteq_plc);
EXPECT_EQ(1, stats.decoded_codec_plc);
EXPECT_EQ(1, stats.decoded_plc_cng);
EXPECT_EQ(1, stats.decoded_muted_output);
}
} // namespace acm2
} // namespace webrtc

View File

@ -0,0 +1,33 @@
# 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.
import("../../webrtc.gni")
audio_codec_defines = []
if (rtc_include_ilbc) {
audio_codec_defines += [ "WEBRTC_CODEC_ILBC" ]
}
if (rtc_include_opus) {
audio_codec_defines += [ "WEBRTC_CODEC_OPUS" ]
}
if (rtc_opus_support_120ms_ptime) {
audio_codec_defines += [ "WEBRTC_OPUS_SUPPORT_120MS_PTIME=1" ]
} else {
audio_codec_defines += [ "WEBRTC_OPUS_SUPPORT_120MS_PTIME=0" ]
}
if (current_cpu == "arm") {
audio_codec_defines += [ "WEBRTC_CODEC_ISACFX" ]
} else {
audio_codec_defines += [ "WEBRTC_CODEC_ISAC" ]
}
if (!build_with_mozilla && !build_with_chromium) {
audio_codec_defines += [ "WEBRTC_CODEC_RED" ]
}
audio_coding_defines = audio_codec_defines
neteq_defines = audio_codec_defines

View File

@ -0,0 +1,34 @@
/*
* 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/audio_network_adaptor/include/audio_network_adaptor_config.h"
namespace webrtc {
AudioEncoderRuntimeConfig::AudioEncoderRuntimeConfig() = default;
AudioEncoderRuntimeConfig::AudioEncoderRuntimeConfig(
const AudioEncoderRuntimeConfig& other) = default;
AudioEncoderRuntimeConfig::~AudioEncoderRuntimeConfig() = default;
AudioEncoderRuntimeConfig& AudioEncoderRuntimeConfig::operator=(
const AudioEncoderRuntimeConfig& other) = default;
bool AudioEncoderRuntimeConfig::operator==(
const AudioEncoderRuntimeConfig& other) const {
return bitrate_bps == other.bitrate_bps &&
frame_length_ms == other.frame_length_ms &&
uplink_packet_loss_fraction == other.uplink_packet_loss_fraction &&
enable_fec == other.enable_fec && enable_dtx == other.enable_dtx &&
num_channels == other.num_channels;
}
} // namespace webrtc

View File

@ -0,0 +1,171 @@
/*
* 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/audio_network_adaptor/audio_network_adaptor_impl.h"
#include <stdint.h>
#include <utility>
#include <vector>
#include "modules/audio_coding/audio_network_adaptor/controller_manager.h"
#include "modules/audio_coding/audio_network_adaptor/debug_dump_writer.h"
#include "modules/audio_coding/audio_network_adaptor/event_log_writer.h"
#include "rtc_base/checks.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/field_trial.h"
namespace webrtc {
namespace {
constexpr int kEventLogMinBitrateChangeBps = 5000;
constexpr float kEventLogMinBitrateChangeFraction = 0.25;
constexpr float kEventLogMinPacketLossChangeFraction = 0.5;
} // namespace
AudioNetworkAdaptorImpl::Config::Config() : event_log(nullptr) {}
AudioNetworkAdaptorImpl::Config::~Config() = default;
AudioNetworkAdaptorImpl::AudioNetworkAdaptorImpl(
const Config& config,
std::unique_ptr<ControllerManager> controller_manager,
std::unique_ptr<DebugDumpWriter> debug_dump_writer)
: config_(config),
controller_manager_(std::move(controller_manager)),
debug_dump_writer_(std::move(debug_dump_writer)),
event_log_writer_(
config.event_log
? new EventLogWriter(config.event_log,
kEventLogMinBitrateChangeBps,
kEventLogMinBitrateChangeFraction,
kEventLogMinPacketLossChangeFraction)
: nullptr) {
RTC_DCHECK(controller_manager_);
}
AudioNetworkAdaptorImpl::~AudioNetworkAdaptorImpl() = default;
void AudioNetworkAdaptorImpl::SetUplinkBandwidth(int uplink_bandwidth_bps) {
last_metrics_.uplink_bandwidth_bps = uplink_bandwidth_bps;
DumpNetworkMetrics();
Controller::NetworkMetrics network_metrics;
network_metrics.uplink_bandwidth_bps = uplink_bandwidth_bps;
UpdateNetworkMetrics(network_metrics);
}
void AudioNetworkAdaptorImpl::SetUplinkPacketLossFraction(
float uplink_packet_loss_fraction) {
last_metrics_.uplink_packet_loss_fraction = uplink_packet_loss_fraction;
DumpNetworkMetrics();
Controller::NetworkMetrics network_metrics;
network_metrics.uplink_packet_loss_fraction = uplink_packet_loss_fraction;
UpdateNetworkMetrics(network_metrics);
}
void AudioNetworkAdaptorImpl::SetRtt(int rtt_ms) {
last_metrics_.rtt_ms = rtt_ms;
DumpNetworkMetrics();
Controller::NetworkMetrics network_metrics;
network_metrics.rtt_ms = rtt_ms;
UpdateNetworkMetrics(network_metrics);
}
void AudioNetworkAdaptorImpl::SetTargetAudioBitrate(
int target_audio_bitrate_bps) {
last_metrics_.target_audio_bitrate_bps = target_audio_bitrate_bps;
DumpNetworkMetrics();
Controller::NetworkMetrics network_metrics;
network_metrics.target_audio_bitrate_bps = target_audio_bitrate_bps;
UpdateNetworkMetrics(network_metrics);
}
void AudioNetworkAdaptorImpl::SetOverhead(size_t overhead_bytes_per_packet) {
last_metrics_.overhead_bytes_per_packet = overhead_bytes_per_packet;
DumpNetworkMetrics();
Controller::NetworkMetrics network_metrics;
network_metrics.overhead_bytes_per_packet = overhead_bytes_per_packet;
UpdateNetworkMetrics(network_metrics);
}
AudioEncoderRuntimeConfig AudioNetworkAdaptorImpl::GetEncoderRuntimeConfig() {
AudioEncoderRuntimeConfig config;
for (auto& controller :
controller_manager_->GetSortedControllers(last_metrics_))
controller->MakeDecision(&config);
// Update ANA stats.
auto increment_opt = [](absl::optional<uint32_t>& a) {
a = a.value_or(0) + 1;
};
if (prev_config_) {
if (config.bitrate_bps != prev_config_->bitrate_bps) {
increment_opt(stats_.bitrate_action_counter);
}
if (config.enable_dtx != prev_config_->enable_dtx) {
increment_opt(stats_.dtx_action_counter);
}
if (config.enable_fec != prev_config_->enable_fec) {
increment_opt(stats_.fec_action_counter);
}
if (config.frame_length_ms && prev_config_->frame_length_ms) {
if (*config.frame_length_ms > *prev_config_->frame_length_ms) {
increment_opt(stats_.frame_length_increase_counter);
} else if (*config.frame_length_ms < *prev_config_->frame_length_ms) {
increment_opt(stats_.frame_length_decrease_counter);
}
}
if (config.num_channels != prev_config_->num_channels) {
increment_opt(stats_.channel_action_counter);
}
if (config.uplink_packet_loss_fraction) {
stats_.uplink_packet_loss_fraction = *config.uplink_packet_loss_fraction;
}
}
prev_config_ = config;
if (debug_dump_writer_)
debug_dump_writer_->DumpEncoderRuntimeConfig(config, rtc::TimeMillis());
if (event_log_writer_)
event_log_writer_->MaybeLogEncoderConfig(config);
return config;
}
void AudioNetworkAdaptorImpl::StartDebugDump(FILE* file_handle) {
debug_dump_writer_ = DebugDumpWriter::Create(file_handle);
}
void AudioNetworkAdaptorImpl::StopDebugDump() {
debug_dump_writer_.reset(nullptr);
}
ANAStats AudioNetworkAdaptorImpl::GetStats() const {
return stats_;
}
void AudioNetworkAdaptorImpl::DumpNetworkMetrics() {
if (debug_dump_writer_)
debug_dump_writer_->DumpNetworkMetrics(last_metrics_, rtc::TimeMillis());
}
void AudioNetworkAdaptorImpl::UpdateNetworkMetrics(
const Controller::NetworkMetrics& network_metrics) {
for (auto& controller : controller_manager_->GetControllers())
controller->UpdateNetworkMetrics(network_metrics);
}
} // namespace webrtc

View File

@ -0,0 +1,89 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_AUDIO_NETWORK_ADAPTOR_IMPL_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_AUDIO_NETWORK_ADAPTOR_IMPL_H_
#include <stdio.h>
#include <memory>
#include "absl/types/optional.h"
#include "api/audio_codecs/audio_encoder.h"
#include "modules/audio_coding/audio_network_adaptor/controller.h"
#include "modules/audio_coding/audio_network_adaptor/debug_dump_writer.h"
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h"
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
class ControllerManager;
class EventLogWriter;
class RtcEventLog;
class AudioNetworkAdaptorImpl final : public AudioNetworkAdaptor {
public:
struct Config {
Config();
~Config();
RtcEventLog* event_log;
};
AudioNetworkAdaptorImpl(
const Config& config,
std::unique_ptr<ControllerManager> controller_manager,
std::unique_ptr<DebugDumpWriter> debug_dump_writer = nullptr);
~AudioNetworkAdaptorImpl() override;
void SetUplinkBandwidth(int uplink_bandwidth_bps) override;
void SetUplinkPacketLossFraction(float uplink_packet_loss_fraction) override;
void SetRtt(int rtt_ms) override;
void SetTargetAudioBitrate(int target_audio_bitrate_bps) override;
void SetOverhead(size_t overhead_bytes_per_packet) override;
AudioEncoderRuntimeConfig GetEncoderRuntimeConfig() override;
void StartDebugDump(FILE* file_handle) override;
void StopDebugDump() override;
ANAStats GetStats() const override;
private:
void DumpNetworkMetrics();
void UpdateNetworkMetrics(const Controller::NetworkMetrics& network_metrics);
const Config config_;
std::unique_ptr<ControllerManager> controller_manager_;
std::unique_ptr<DebugDumpWriter> debug_dump_writer_;
const std::unique_ptr<EventLogWriter> event_log_writer_;
Controller::NetworkMetrics last_metrics_;
absl::optional<AudioEncoderRuntimeConfig> prev_config_;
ANAStats stats_;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioNetworkAdaptorImpl);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_AUDIO_NETWORK_ADAPTOR_IMPL_H_

View File

@ -0,0 +1,306 @@
/*
* 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/audio_network_adaptor/audio_network_adaptor_impl.h"
#include <utility>
#include <vector>
#include "api/rtc_event_log/rtc_event.h"
#include "logging/rtc_event_log/events/rtc_event_audio_network_adaptation.h"
#include "logging/rtc_event_log/mock/mock_rtc_event_log.h"
#include "modules/audio_coding/audio_network_adaptor/mock/mock_controller.h"
#include "modules/audio_coding/audio_network_adaptor/mock/mock_controller_manager.h"
#include "modules/audio_coding/audio_network_adaptor/mock/mock_debug_dump_writer.h"
#include "rtc_base/fake_clock.h"
#include "test/field_trial.h"
#include "test/gtest.h"
namespace webrtc {
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace {
constexpr size_t kNumControllers = 2;
constexpr int64_t kClockInitialTimeMs = 12345678;
MATCHER_P(NetworkMetricsIs, metric, "") {
return arg.uplink_bandwidth_bps == metric.uplink_bandwidth_bps &&
arg.target_audio_bitrate_bps == metric.target_audio_bitrate_bps &&
arg.rtt_ms == metric.rtt_ms &&
arg.overhead_bytes_per_packet == metric.overhead_bytes_per_packet &&
arg.uplink_packet_loss_fraction == metric.uplink_packet_loss_fraction;
}
MATCHER_P(IsRtcEventAnaConfigEqualTo, config, "") {
if (arg->GetType() != RtcEvent::Type::AudioNetworkAdaptation) {
return false;
}
auto ana_event = static_cast<RtcEventAudioNetworkAdaptation*>(arg);
return ana_event->config() == config;
}
MATCHER_P(EncoderRuntimeConfigIs, config, "") {
return arg.bitrate_bps == config.bitrate_bps &&
arg.frame_length_ms == config.frame_length_ms &&
arg.uplink_packet_loss_fraction ==
config.uplink_packet_loss_fraction &&
arg.enable_fec == config.enable_fec &&
arg.enable_dtx == config.enable_dtx &&
arg.num_channels == config.num_channels;
}
struct AudioNetworkAdaptorStates {
std::unique_ptr<AudioNetworkAdaptorImpl> audio_network_adaptor;
std::vector<std::unique_ptr<MockController>> mock_controllers;
std::unique_ptr<MockRtcEventLog> event_log;
MockDebugDumpWriter* mock_debug_dump_writer;
};
AudioNetworkAdaptorStates CreateAudioNetworkAdaptor() {
AudioNetworkAdaptorStates states;
std::vector<Controller*> controllers;
for (size_t i = 0; i < kNumControllers; ++i) {
auto controller =
std::unique_ptr<MockController>(new NiceMock<MockController>());
EXPECT_CALL(*controller, Die());
controllers.push_back(controller.get());
states.mock_controllers.push_back(std::move(controller));
}
auto controller_manager = std::unique_ptr<MockControllerManager>(
new NiceMock<MockControllerManager>());
EXPECT_CALL(*controller_manager, Die());
EXPECT_CALL(*controller_manager, GetControllers())
.WillRepeatedly(Return(controllers));
EXPECT_CALL(*controller_manager, GetSortedControllers(_))
.WillRepeatedly(Return(controllers));
states.event_log.reset(new NiceMock<MockRtcEventLog>());
auto debug_dump_writer =
std::unique_ptr<MockDebugDumpWriter>(new NiceMock<MockDebugDumpWriter>());
EXPECT_CALL(*debug_dump_writer, Die());
states.mock_debug_dump_writer = debug_dump_writer.get();
AudioNetworkAdaptorImpl::Config config;
config.event_log = states.event_log.get();
// AudioNetworkAdaptorImpl governs the lifetime of controller manager.
states.audio_network_adaptor.reset(new AudioNetworkAdaptorImpl(
config, std::move(controller_manager), std::move(debug_dump_writer)));
return states;
}
void SetExpectCallToUpdateNetworkMetrics(
const std::vector<std::unique_ptr<MockController>>& controllers,
const Controller::NetworkMetrics& check) {
for (auto& mock_controller : controllers) {
EXPECT_CALL(*mock_controller,
UpdateNetworkMetrics(NetworkMetricsIs(check)));
}
}
} // namespace
TEST(AudioNetworkAdaptorImplTest,
UpdateNetworkMetricsIsCalledOnSetUplinkBandwidth) {
auto states = CreateAudioNetworkAdaptor();
constexpr int kBandwidth = 16000;
Controller::NetworkMetrics check;
check.uplink_bandwidth_bps = kBandwidth;
SetExpectCallToUpdateNetworkMetrics(states.mock_controllers, check);
states.audio_network_adaptor->SetUplinkBandwidth(kBandwidth);
}
TEST(AudioNetworkAdaptorImplTest,
UpdateNetworkMetricsIsCalledOnSetUplinkPacketLossFraction) {
auto states = CreateAudioNetworkAdaptor();
constexpr float kPacketLoss = 0.7f;
Controller::NetworkMetrics check;
check.uplink_packet_loss_fraction = kPacketLoss;
SetExpectCallToUpdateNetworkMetrics(states.mock_controllers, check);
states.audio_network_adaptor->SetUplinkPacketLossFraction(kPacketLoss);
}
TEST(AudioNetworkAdaptorImplTest, UpdateNetworkMetricsIsCalledOnSetRtt) {
auto states = CreateAudioNetworkAdaptor();
constexpr int kRtt = 100;
Controller::NetworkMetrics check;
check.rtt_ms = kRtt;
SetExpectCallToUpdateNetworkMetrics(states.mock_controllers, check);
states.audio_network_adaptor->SetRtt(kRtt);
}
TEST(AudioNetworkAdaptorImplTest,
UpdateNetworkMetricsIsCalledOnSetTargetAudioBitrate) {
auto states = CreateAudioNetworkAdaptor();
constexpr int kTargetAudioBitrate = 15000;
Controller::NetworkMetrics check;
check.target_audio_bitrate_bps = kTargetAudioBitrate;
SetExpectCallToUpdateNetworkMetrics(states.mock_controllers, check);
states.audio_network_adaptor->SetTargetAudioBitrate(kTargetAudioBitrate);
}
TEST(AudioNetworkAdaptorImplTest, UpdateNetworkMetricsIsCalledOnSetOverhead) {
auto states = CreateAudioNetworkAdaptor();
constexpr size_t kOverhead = 64;
Controller::NetworkMetrics check;
check.overhead_bytes_per_packet = kOverhead;
SetExpectCallToUpdateNetworkMetrics(states.mock_controllers, check);
states.audio_network_adaptor->SetOverhead(kOverhead);
}
TEST(AudioNetworkAdaptorImplTest,
MakeDecisionIsCalledOnGetEncoderRuntimeConfig) {
auto states = CreateAudioNetworkAdaptor();
for (auto& mock_controller : states.mock_controllers)
EXPECT_CALL(*mock_controller, MakeDecision(_));
states.audio_network_adaptor->GetEncoderRuntimeConfig();
}
TEST(AudioNetworkAdaptorImplTest,
DumpEncoderRuntimeConfigIsCalledOnGetEncoderRuntimeConfig) {
test::ScopedFieldTrials override_field_trials(
"WebRTC-Audio-BitrateAdaptation/Enabled/WebRTC-Audio-FecAdaptation/"
"Enabled/");
rtc::ScopedFakeClock fake_clock;
fake_clock.AdvanceTime(TimeDelta::Millis(kClockInitialTimeMs));
auto states = CreateAudioNetworkAdaptor();
AudioEncoderRuntimeConfig config;
config.bitrate_bps = 32000;
config.enable_fec = true;
EXPECT_CALL(*states.mock_controllers[0], MakeDecision(_))
.WillOnce(SetArgPointee<0>(config));
EXPECT_CALL(*states.mock_debug_dump_writer,
DumpEncoderRuntimeConfig(EncoderRuntimeConfigIs(config),
kClockInitialTimeMs));
states.audio_network_adaptor->GetEncoderRuntimeConfig();
}
TEST(AudioNetworkAdaptorImplTest,
DumpNetworkMetricsIsCalledOnSetNetworkMetrics) {
rtc::ScopedFakeClock fake_clock;
fake_clock.AdvanceTime(TimeDelta::Millis(kClockInitialTimeMs));
auto states = CreateAudioNetworkAdaptor();
constexpr int kBandwidth = 16000;
constexpr float kPacketLoss = 0.7f;
constexpr int kRtt = 100;
constexpr int kTargetAudioBitrate = 15000;
constexpr size_t kOverhead = 64;
Controller::NetworkMetrics check;
check.uplink_bandwidth_bps = kBandwidth;
int64_t timestamp_check = kClockInitialTimeMs;
EXPECT_CALL(*states.mock_debug_dump_writer,
DumpNetworkMetrics(NetworkMetricsIs(check), timestamp_check));
states.audio_network_adaptor->SetUplinkBandwidth(kBandwidth);
fake_clock.AdvanceTime(TimeDelta::Millis(100));
timestamp_check += 100;
check.uplink_packet_loss_fraction = kPacketLoss;
EXPECT_CALL(*states.mock_debug_dump_writer,
DumpNetworkMetrics(NetworkMetricsIs(check), timestamp_check));
states.audio_network_adaptor->SetUplinkPacketLossFraction(kPacketLoss);
fake_clock.AdvanceTime(TimeDelta::Millis(50));
timestamp_check += 50;
fake_clock.AdvanceTime(TimeDelta::Millis(200));
timestamp_check += 200;
check.rtt_ms = kRtt;
EXPECT_CALL(*states.mock_debug_dump_writer,
DumpNetworkMetrics(NetworkMetricsIs(check), timestamp_check));
states.audio_network_adaptor->SetRtt(kRtt);
fake_clock.AdvanceTime(TimeDelta::Millis(150));
timestamp_check += 150;
check.target_audio_bitrate_bps = kTargetAudioBitrate;
EXPECT_CALL(*states.mock_debug_dump_writer,
DumpNetworkMetrics(NetworkMetricsIs(check), timestamp_check));
states.audio_network_adaptor->SetTargetAudioBitrate(kTargetAudioBitrate);
fake_clock.AdvanceTime(TimeDelta::Millis(50));
timestamp_check += 50;
check.overhead_bytes_per_packet = kOverhead;
EXPECT_CALL(*states.mock_debug_dump_writer,
DumpNetworkMetrics(NetworkMetricsIs(check), timestamp_check));
states.audio_network_adaptor->SetOverhead(kOverhead);
}
TEST(AudioNetworkAdaptorImplTest, LogRuntimeConfigOnGetEncoderRuntimeConfig) {
test::ScopedFieldTrials override_field_trials(
"WebRTC-Audio-BitrateAdaptation/Enabled/WebRTC-Audio-FecAdaptation/"
"Enabled/");
auto states = CreateAudioNetworkAdaptor();
AudioEncoderRuntimeConfig config;
config.bitrate_bps = 32000;
config.enable_fec = true;
EXPECT_CALL(*states.mock_controllers[0], MakeDecision(_))
.WillOnce(SetArgPointee<0>(config));
EXPECT_CALL(*states.event_log, LogProxy(IsRtcEventAnaConfigEqualTo(config)))
.Times(1);
states.audio_network_adaptor->GetEncoderRuntimeConfig();
}
TEST(AudioNetworkAdaptorImplTest, TestANAStats) {
auto states = CreateAudioNetworkAdaptor();
// Simulate some adaptation, otherwise the stats will not show anything.
AudioEncoderRuntimeConfig config1, config2;
config1.bitrate_bps = 32000;
config1.num_channels = 2;
config1.enable_fec = true;
config1.enable_dtx = true;
config1.frame_length_ms = 120;
config1.uplink_packet_loss_fraction = 0.1f;
config2.bitrate_bps = 16000;
config2.num_channels = 1;
config2.enable_fec = false;
config2.enable_dtx = false;
config2.frame_length_ms = 60;
config1.uplink_packet_loss_fraction = 0.1f;
EXPECT_CALL(*states.mock_controllers[0], MakeDecision(_))
.WillOnce(SetArgPointee<0>(config1));
states.audio_network_adaptor->GetEncoderRuntimeConfig();
EXPECT_CALL(*states.mock_controllers[0], MakeDecision(_))
.WillOnce(SetArgPointee<0>(config2));
states.audio_network_adaptor->GetEncoderRuntimeConfig();
EXPECT_CALL(*states.mock_controllers[0], MakeDecision(_))
.WillOnce(SetArgPointee<0>(config1));
states.audio_network_adaptor->GetEncoderRuntimeConfig();
auto ana_stats = states.audio_network_adaptor->GetStats();
EXPECT_EQ(ana_stats.bitrate_action_counter, 2u);
EXPECT_EQ(ana_stats.channel_action_counter, 2u);
EXPECT_EQ(ana_stats.dtx_action_counter, 2u);
EXPECT_EQ(ana_stats.fec_action_counter, 2u);
EXPECT_EQ(ana_stats.frame_length_increase_counter, 1u);
EXPECT_EQ(ana_stats.frame_length_decrease_counter, 1u);
EXPECT_EQ(ana_stats.uplink_packet_loss_fraction, 0.1f);
}
} // namespace webrtc

View File

@ -0,0 +1,78 @@
/*
* 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/audio_network_adaptor/bitrate_controller.h"
#include <algorithm>
#include "rtc_base/checks.h"
#include "system_wrappers/include/field_trial.h"
namespace webrtc {
namespace audio_network_adaptor {
BitrateController::Config::Config(int initial_bitrate_bps,
int initial_frame_length_ms,
int fl_increase_overhead_offset,
int fl_decrease_overhead_offset)
: initial_bitrate_bps(initial_bitrate_bps),
initial_frame_length_ms(initial_frame_length_ms),
fl_increase_overhead_offset(fl_increase_overhead_offset),
fl_decrease_overhead_offset(fl_decrease_overhead_offset) {}
BitrateController::Config::~Config() = default;
BitrateController::BitrateController(const Config& config)
: config_(config),
bitrate_bps_(config_.initial_bitrate_bps),
frame_length_ms_(config_.initial_frame_length_ms) {
RTC_DCHECK_GT(bitrate_bps_, 0);
RTC_DCHECK_GT(frame_length_ms_, 0);
}
BitrateController::~BitrateController() = default;
void BitrateController::UpdateNetworkMetrics(
const NetworkMetrics& network_metrics) {
if (network_metrics.target_audio_bitrate_bps)
target_audio_bitrate_bps_ = network_metrics.target_audio_bitrate_bps;
if (network_metrics.overhead_bytes_per_packet) {
RTC_DCHECK_GT(*network_metrics.overhead_bytes_per_packet, 0);
overhead_bytes_per_packet_ = network_metrics.overhead_bytes_per_packet;
}
}
void BitrateController::MakeDecision(AudioEncoderRuntimeConfig* config) {
// Decision on |bitrate_bps| should not have been made.
RTC_DCHECK(!config->bitrate_bps);
if (target_audio_bitrate_bps_ && overhead_bytes_per_packet_) {
// Current implementation of BitrateController can only work when
// |metrics.target_audio_bitrate_bps| includes overhead is enabled. This is
// currently governed by the following field trial.
RTC_DCHECK(
webrtc::field_trial::IsEnabled("WebRTC-SendSideBwe-WithOverhead"));
if (config->frame_length_ms)
frame_length_ms_ = *config->frame_length_ms;
int offset = config->last_fl_change_increase
? config_.fl_increase_overhead_offset
: config_.fl_decrease_overhead_offset;
// Check that
// -(*overhead_bytes_per_packet_) <= offset <= (*overhead_bytes_per_packet_)
RTC_DCHECK_GE(*overhead_bytes_per_packet_, -offset);
RTC_DCHECK_LE(offset, *overhead_bytes_per_packet_);
int overhead_rate_bps = static_cast<int>(
(*overhead_bytes_per_packet_ + offset) * 8 * 1000 / frame_length_ms_);
bitrate_bps_ = std::max(0, *target_audio_bitrate_bps_ - overhead_rate_bps);
}
config->bitrate_bps = bitrate_bps_;
}
} // namespace audio_network_adaptor
} // namespace webrtc

View File

@ -0,0 +1,58 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_BITRATE_CONTROLLER_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_BITRATE_CONTROLLER_H_
#include <stddef.h>
#include "absl/types/optional.h"
#include "modules/audio_coding/audio_network_adaptor/controller.h"
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
namespace audio_network_adaptor {
class BitrateController final : public Controller {
public:
struct Config {
Config(int initial_bitrate_bps,
int initial_frame_length_ms,
int fl_increase_overhead_offset,
int fl_decrease_overhead_offset);
~Config();
int initial_bitrate_bps;
int initial_frame_length_ms;
int fl_increase_overhead_offset;
int fl_decrease_overhead_offset;
};
explicit BitrateController(const Config& config);
~BitrateController() override;
void UpdateNetworkMetrics(const NetworkMetrics& network_metrics) override;
void MakeDecision(AudioEncoderRuntimeConfig* config) override;
private:
const Config config_;
int bitrate_bps_;
int frame_length_ms_;
absl::optional<int> target_audio_bitrate_bps_;
absl::optional<size_t> overhead_bytes_per_packet_;
RTC_DISALLOW_COPY_AND_ASSIGN(BitrateController);
};
} // namespace audio_network_adaptor
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_BITRATE_CONTROLLER_H_

View File

@ -0,0 +1,250 @@
/*
* 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/audio_network_adaptor/bitrate_controller.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "test/field_trial.h"
#include "test/gtest.h"
namespace webrtc {
namespace audio_network_adaptor {
namespace {
void UpdateNetworkMetrics(
BitrateController* controller,
const absl::optional<int>& target_audio_bitrate_bps,
const absl::optional<size_t>& overhead_bytes_per_packet) {
// UpdateNetworkMetrics can accept multiple network metric updates at once.
// However, currently, the most used case is to update one metric at a time.
// To reflect this fact, we separate the calls.
if (target_audio_bitrate_bps) {
Controller::NetworkMetrics network_metrics;
network_metrics.target_audio_bitrate_bps = target_audio_bitrate_bps;
controller->UpdateNetworkMetrics(network_metrics);
}
if (overhead_bytes_per_packet) {
Controller::NetworkMetrics network_metrics;
network_metrics.overhead_bytes_per_packet = overhead_bytes_per_packet;
controller->UpdateNetworkMetrics(network_metrics);
}
}
void CheckDecision(BitrateController* controller,
const absl::optional<int>& frame_length_ms,
int expected_bitrate_bps) {
AudioEncoderRuntimeConfig config;
config.frame_length_ms = frame_length_ms;
controller->MakeDecision(&config);
EXPECT_EQ(expected_bitrate_bps, config.bitrate_bps);
}
} // namespace
// These tests are named AnaBitrateControllerTest to distinguish from
// BitrateControllerTest in
// modules/bitrate_controller/bitrate_controller_unittest.cc.
TEST(AnaBitrateControllerTest, OutputInitValueWhenTargetBitrateUnknown) {
constexpr int kInitialBitrateBps = 32000;
constexpr int kInitialFrameLengthMs = 20;
constexpr size_t kOverheadBytesPerPacket = 64;
BitrateController controller(BitrateController::Config(
kInitialBitrateBps, kInitialFrameLengthMs, 0, 0));
UpdateNetworkMetrics(&controller, absl::nullopt, kOverheadBytesPerPacket);
CheckDecision(&controller, kInitialFrameLengthMs * 2, kInitialBitrateBps);
}
TEST(AnaBitrateControllerTest, OutputInitValueWhenOverheadUnknown) {
constexpr int kInitialBitrateBps = 32000;
constexpr int kInitialFrameLengthMs = 20;
constexpr int kTargetBitrateBps = 48000;
BitrateController controller(BitrateController::Config(
kInitialBitrateBps, kInitialFrameLengthMs, 0, 0));
UpdateNetworkMetrics(&controller, kTargetBitrateBps, absl::nullopt);
CheckDecision(&controller, kInitialFrameLengthMs * 2, kInitialBitrateBps);
}
TEST(AnaBitrateControllerTest, ChangeBitrateOnTargetBitrateChanged) {
test::ScopedFieldTrials override_field_trials(
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
constexpr int kInitialFrameLengthMs = 20;
BitrateController controller(
BitrateController::Config(32000, kInitialFrameLengthMs, 0, 0));
constexpr int kTargetBitrateBps = 48000;
constexpr size_t kOverheadBytesPerPacket = 64;
constexpr int kBitrateBps = kTargetBitrateBps - kOverheadBytesPerPacket * 8 *
1000 /
kInitialFrameLengthMs;
// Frame length unchanged, bitrate changes in accordance with
// |metrics.target_audio_bitrate_bps| and |metrics.overhead_bytes_per_packet|.
UpdateNetworkMetrics(&controller, kTargetBitrateBps, kOverheadBytesPerPacket);
CheckDecision(&controller, kInitialFrameLengthMs, kBitrateBps);
}
TEST(AnaBitrateControllerTest, UpdateMultipleNetworkMetricsAtOnce) {
// This test is similar to ChangeBitrateOnTargetBitrateChanged. But instead of
// using ::UpdateNetworkMetrics(...), which calls
// BitrateController::UpdateNetworkMetrics(...) multiple times, we
// we call it only once. This is to verify that
// BitrateController::UpdateNetworkMetrics(...) can handle multiple
// network updates at once. This is, however, not a common use case in current
// audio_network_adaptor_impl.cc.
test::ScopedFieldTrials override_field_trials(
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
constexpr int kInitialFrameLengthMs = 20;
BitrateController controller(
BitrateController::Config(32000, kInitialFrameLengthMs, 0, 0));
constexpr int kTargetBitrateBps = 48000;
constexpr size_t kOverheadBytesPerPacket = 64;
constexpr int kBitrateBps = kTargetBitrateBps - kOverheadBytesPerPacket * 8 *
1000 /
kInitialFrameLengthMs;
Controller::NetworkMetrics network_metrics;
network_metrics.target_audio_bitrate_bps = kTargetBitrateBps;
network_metrics.overhead_bytes_per_packet = kOverheadBytesPerPacket;
controller.UpdateNetworkMetrics(network_metrics);
CheckDecision(&controller, kInitialFrameLengthMs, kBitrateBps);
}
TEST(AnaBitrateControllerTest, TreatUnknownFrameLengthAsFrameLengthUnchanged) {
test::ScopedFieldTrials override_field_trials(
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
constexpr int kInitialFrameLengthMs = 20;
BitrateController controller(
BitrateController::Config(32000, kInitialFrameLengthMs, 0, 0));
constexpr int kTargetBitrateBps = 48000;
constexpr size_t kOverheadBytesPerPacket = 64;
constexpr int kBitrateBps = kTargetBitrateBps - kOverheadBytesPerPacket * 8 *
1000 /
kInitialFrameLengthMs;
UpdateNetworkMetrics(&controller, kTargetBitrateBps, kOverheadBytesPerPacket);
CheckDecision(&controller, absl::nullopt, kBitrateBps);
}
TEST(AnaBitrateControllerTest, IncreaseBitrateOnFrameLengthIncreased) {
test::ScopedFieldTrials override_field_trials(
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
constexpr int kInitialFrameLengthMs = 20;
BitrateController controller(
BitrateController::Config(32000, kInitialFrameLengthMs, 0, 0));
constexpr int kTargetBitrateBps = 48000;
constexpr size_t kOverheadBytesPerPacket = 64;
constexpr int kBitrateBps = kTargetBitrateBps - kOverheadBytesPerPacket * 8 *
1000 /
kInitialFrameLengthMs;
UpdateNetworkMetrics(&controller, kTargetBitrateBps, kOverheadBytesPerPacket);
CheckDecision(&controller, absl::nullopt, kBitrateBps);
constexpr int kFrameLengthMs = 60;
constexpr size_t kPacketOverheadRateDiff =
kOverheadBytesPerPacket * 8 * 1000 / 20 -
kOverheadBytesPerPacket * 8 * 1000 / 60;
UpdateNetworkMetrics(&controller, kTargetBitrateBps, kOverheadBytesPerPacket);
CheckDecision(&controller, kFrameLengthMs,
kBitrateBps + kPacketOverheadRateDiff);
}
TEST(AnaBitrateControllerTest, DecreaseBitrateOnFrameLengthDecreased) {
test::ScopedFieldTrials override_field_trials(
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
constexpr int kInitialFrameLengthMs = 60;
BitrateController controller(
BitrateController::Config(32000, kInitialFrameLengthMs, 0, 0));
constexpr int kTargetBitrateBps = 48000;
constexpr size_t kOverheadBytesPerPacket = 64;
constexpr int kBitrateBps = kTargetBitrateBps - kOverheadBytesPerPacket * 8 *
1000 /
kInitialFrameLengthMs;
UpdateNetworkMetrics(&controller, kTargetBitrateBps, kOverheadBytesPerPacket);
CheckDecision(&controller, absl::nullopt, kBitrateBps);
constexpr int kFrameLengthMs = 20;
constexpr size_t kPacketOverheadRateDiff =
kOverheadBytesPerPacket * 8 * 1000 / 20 -
kOverheadBytesPerPacket * 8 * 1000 / 60;
UpdateNetworkMetrics(&controller, kTargetBitrateBps, kOverheadBytesPerPacket);
CheckDecision(&controller, kFrameLengthMs,
kBitrateBps - kPacketOverheadRateDiff);
}
TEST(AnaBitrateControllerTest, BitrateNeverBecomesNegative) {
test::ScopedFieldTrials override_field_trials(
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
BitrateController controller(BitrateController::Config(32000, 20, 0, 0));
constexpr size_t kOverheadBytesPerPacket = 64;
constexpr int kFrameLengthMs = 60;
// Set a target rate smaller than overhead rate, the bitrate is bounded by 0.
constexpr int kTargetBitrateBps =
kOverheadBytesPerPacket * 8 * 1000 / kFrameLengthMs - 1;
UpdateNetworkMetrics(&controller, kTargetBitrateBps, kOverheadBytesPerPacket);
CheckDecision(&controller, kFrameLengthMs, 0);
}
TEST(AnaBitrateControllerTest, CheckBehaviorOnChangingCondition) {
test::ScopedFieldTrials override_field_trials(
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
BitrateController controller(BitrateController::Config(32000, 20, 0, 0));
// Start from an arbitrary overall bitrate.
int overall_bitrate = 34567;
size_t overhead_bytes_per_packet = 64;
int frame_length_ms = 20;
int current_bitrate = rtc::checked_cast<int>(
overall_bitrate - overhead_bytes_per_packet * 8 * 1000 / frame_length_ms);
UpdateNetworkMetrics(&controller, overall_bitrate, overhead_bytes_per_packet);
CheckDecision(&controller, frame_length_ms, current_bitrate);
// Next: increase overall bitrate.
overall_bitrate += 100;
current_bitrate += 100;
UpdateNetworkMetrics(&controller, overall_bitrate, overhead_bytes_per_packet);
CheckDecision(&controller, frame_length_ms, current_bitrate);
// Next: change frame length.
frame_length_ms = 60;
current_bitrate +=
rtc::checked_cast<int>(overhead_bytes_per_packet * 8 * 1000 / 20 -
overhead_bytes_per_packet * 8 * 1000 / 60);
UpdateNetworkMetrics(&controller, overall_bitrate, overhead_bytes_per_packet);
CheckDecision(&controller, frame_length_ms, current_bitrate);
// Next: change overhead.
overhead_bytes_per_packet -= 30;
current_bitrate += 30 * 8 * 1000 / frame_length_ms;
UpdateNetworkMetrics(&controller, overall_bitrate, overhead_bytes_per_packet);
CheckDecision(&controller, frame_length_ms, current_bitrate);
// Next: change frame length.
frame_length_ms = 20;
current_bitrate -=
rtc::checked_cast<int>(overhead_bytes_per_packet * 8 * 1000 / 20 -
overhead_bytes_per_packet * 8 * 1000 / 60);
UpdateNetworkMetrics(&controller, overall_bitrate, overhead_bytes_per_packet);
CheckDecision(&controller, frame_length_ms, current_bitrate);
// Next: decrease overall bitrate and frame length.
overall_bitrate -= 100;
current_bitrate -= 100;
frame_length_ms = 60;
current_bitrate +=
rtc::checked_cast<int>(overhead_bytes_per_packet * 8 * 1000 / 20 -
overhead_bytes_per_packet * 8 * 1000 / 60);
UpdateNetworkMetrics(&controller, overall_bitrate, overhead_bytes_per_packet);
CheckDecision(&controller, frame_length_ms, current_bitrate);
}
} // namespace audio_network_adaptor
} // namespace webrtc

View File

@ -0,0 +1,62 @@
/*
* 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/audio_network_adaptor/channel_controller.h"
#include <algorithm>
#include "rtc_base/checks.h"
namespace webrtc {
ChannelController::Config::Config(size_t num_encoder_channels,
size_t intial_channels_to_encode,
int channel_1_to_2_bandwidth_bps,
int channel_2_to_1_bandwidth_bps)
: num_encoder_channels(num_encoder_channels),
intial_channels_to_encode(intial_channels_to_encode),
channel_1_to_2_bandwidth_bps(channel_1_to_2_bandwidth_bps),
channel_2_to_1_bandwidth_bps(channel_2_to_1_bandwidth_bps) {}
ChannelController::ChannelController(const Config& config)
: config_(config), channels_to_encode_(config_.intial_channels_to_encode) {
RTC_DCHECK_GT(config_.intial_channels_to_encode, 0lu);
// Currently, we require |intial_channels_to_encode| to be <= 2.
RTC_DCHECK_LE(config_.intial_channels_to_encode, 2lu);
RTC_DCHECK_GE(config_.num_encoder_channels,
config_.intial_channels_to_encode);
}
ChannelController::~ChannelController() = default;
void ChannelController::UpdateNetworkMetrics(
const NetworkMetrics& network_metrics) {
if (network_metrics.uplink_bandwidth_bps)
uplink_bandwidth_bps_ = network_metrics.uplink_bandwidth_bps;
}
void ChannelController::MakeDecision(AudioEncoderRuntimeConfig* config) {
// Decision on |num_channels| should not have been made.
RTC_DCHECK(!config->num_channels);
if (uplink_bandwidth_bps_) {
if (channels_to_encode_ == 2 &&
*uplink_bandwidth_bps_ <= config_.channel_2_to_1_bandwidth_bps) {
channels_to_encode_ = 1;
} else if (channels_to_encode_ == 1 &&
*uplink_bandwidth_bps_ >= config_.channel_1_to_2_bandwidth_bps) {
channels_to_encode_ =
std::min(static_cast<size_t>(2), config_.num_encoder_channels);
}
}
config->num_channels = channels_to_encode_;
}
} // namespace webrtc

View File

@ -0,0 +1,57 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_CHANNEL_CONTROLLER_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_CHANNEL_CONTROLLER_H_
#include <stddef.h>
#include "absl/types/optional.h"
#include "modules/audio_coding/audio_network_adaptor/controller.h"
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
class ChannelController final : public Controller {
public:
struct Config {
Config(size_t num_encoder_channels,
size_t intial_channels_to_encode,
int channel_1_to_2_bandwidth_bps,
int channel_2_to_1_bandwidth_bps);
size_t num_encoder_channels;
size_t intial_channels_to_encode;
// Uplink bandwidth above which the number of encoded channels should switch
// from 1 to 2.
int channel_1_to_2_bandwidth_bps;
// Uplink bandwidth below which the number of encoded channels should switch
// from 2 to 1.
int channel_2_to_1_bandwidth_bps;
};
explicit ChannelController(const Config& config);
~ChannelController() override;
void UpdateNetworkMetrics(const NetworkMetrics& network_metrics) override;
void MakeDecision(AudioEncoderRuntimeConfig* config) override;
private:
const Config config_;
size_t channels_to_encode_;
absl::optional<int> uplink_bandwidth_bps_;
RTC_DISALLOW_COPY_AND_ASSIGN(ChannelController);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_CHANNEL_CONTROLLER_H_

View File

@ -0,0 +1,101 @@
/*
* 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/audio_network_adaptor/channel_controller.h"
#include <memory>
#include "test/gtest.h"
namespace webrtc {
namespace {
constexpr int kNumChannels = 2;
constexpr int kChannel1To2BandwidthBps = 31000;
constexpr int kChannel2To1BandwidthBps = 29000;
constexpr int kMediumBandwidthBps =
(kChannel1To2BandwidthBps + kChannel2To1BandwidthBps) / 2;
std::unique_ptr<ChannelController> CreateChannelController(int init_channels) {
std::unique_ptr<ChannelController> controller(
new ChannelController(ChannelController::Config(
kNumChannels, init_channels, kChannel1To2BandwidthBps,
kChannel2To1BandwidthBps)));
return controller;
}
void CheckDecision(ChannelController* controller,
const absl::optional<int>& uplink_bandwidth_bps,
size_t expected_num_channels) {
if (uplink_bandwidth_bps) {
Controller::NetworkMetrics network_metrics;
network_metrics.uplink_bandwidth_bps = uplink_bandwidth_bps;
controller->UpdateNetworkMetrics(network_metrics);
}
AudioEncoderRuntimeConfig config;
controller->MakeDecision(&config);
EXPECT_EQ(expected_num_channels, config.num_channels);
}
} // namespace
TEST(ChannelControllerTest, OutputInitValueWhenUplinkBandwidthUnknown) {
constexpr int kInitChannels = 2;
auto controller = CreateChannelController(kInitChannels);
CheckDecision(controller.get(), absl::nullopt, kInitChannels);
}
TEST(ChannelControllerTest, SwitchTo2ChannelsOnHighUplinkBandwidth) {
constexpr int kInitChannels = 1;
auto controller = CreateChannelController(kInitChannels);
// Use high bandwidth to check output switch to 2.
CheckDecision(controller.get(), kChannel1To2BandwidthBps, 2);
}
TEST(ChannelControllerTest, SwitchTo1ChannelOnLowUplinkBandwidth) {
constexpr int kInitChannels = 2;
auto controller = CreateChannelController(kInitChannels);
// Use low bandwidth to check output switch to 1.
CheckDecision(controller.get(), kChannel2To1BandwidthBps, 1);
}
TEST(ChannelControllerTest, Maintain1ChannelOnMediumUplinkBandwidth) {
constexpr int kInitChannels = 1;
auto controller = CreateChannelController(kInitChannels);
// Use between-thresholds bandwidth to check output remains at 1.
CheckDecision(controller.get(), kMediumBandwidthBps, 1);
}
TEST(ChannelControllerTest, Maintain2ChannelsOnMediumUplinkBandwidth) {
constexpr int kInitChannels = 2;
auto controller = CreateChannelController(kInitChannels);
// Use between-thresholds bandwidth to check output remains at 2.
CheckDecision(controller.get(), kMediumBandwidthBps, 2);
}
TEST(ChannelControllerTest, CheckBehaviorOnChangingUplinkBandwidth) {
constexpr int kInitChannels = 1;
auto controller = CreateChannelController(kInitChannels);
// Use between-thresholds bandwidth to check output remains at 1.
CheckDecision(controller.get(), kMediumBandwidthBps, 1);
// Use high bandwidth to check output switch to 2.
CheckDecision(controller.get(), kChannel1To2BandwidthBps, 2);
// Use between-thresholds bandwidth to check output remains at 2.
CheckDecision(controller.get(), kMediumBandwidthBps, 2);
// Use low bandwidth to check output switch to 1.
CheckDecision(controller.get(), kChannel2To1BandwidthBps, 1);
}
} // namespace webrtc

View File

@ -0,0 +1,180 @@
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
option java_package = "org.webrtc.AudioNetworkAdaptor";
option java_outer_classname = "Config";
package webrtc.audio_network_adaptor.config;
message FecController {
message Threshold {
// Threshold defines a curve in the bandwidth/packet-loss domain. The
// curve is characterized by the two conjunction points: A and B.
//
// packet ^ |
// loss | A|
// | \ A: (low_bandwidth_bps, low_bandwidth_packet_loss)
// | \ B: (high_bandwidth_bps, high_bandwidth_packet_loss)
// | B\________
// |---------------> bandwidth
optional int32 low_bandwidth_bps = 1;
optional float low_bandwidth_packet_loss = 2;
optional int32 high_bandwidth_bps = 3;
optional float high_bandwidth_packet_loss = 4;
}
// |fec_enabling_threshold| defines a curve, above which FEC should be
// enabled. |fec_disabling_threshold| defines a curve, under which FEC
// should be disabled. See below
//
// packet-loss ^ | |
// | | | FEC
// | \ \ ON
// | FEC \ \_______ fec_enabling_threshold
// | OFF \_________ fec_disabling_threshold
// |-----------------> bandwidth
optional Threshold fec_enabling_threshold = 1;
optional Threshold fec_disabling_threshold = 2;
// |time_constant_ms| is the time constant for an exponential filter, which
// is used for smoothing the packet loss fraction.
optional int32 time_constant_ms = 3;
}
message FecControllerRplrBased {
message Threshold {
// Threshold defines a curve in the bandwidth/recoverable-packet-loss
// domain.
// The curve is characterized by the two conjunction points: A and B.
//
// recoverable ^
// packet | |
// loss | A|
// | \ A: (low_bandwidth_bps,
// | \ low_bandwidth_recoverable_packet_loss)
// | \ B: (high_bandwidth_bps,
// | \ high_bandwidth_recoverable_packet_loss)
// | B\________
// |---------------> bandwidth
optional int32 low_bandwidth_bps = 1;
optional float low_bandwidth_recoverable_packet_loss = 2;
optional int32 high_bandwidth_bps = 3;
optional float high_bandwidth_recoverable_packet_loss = 4;
}
// |fec_enabling_threshold| defines a curve, above which FEC should be
// enabled. |fec_disabling_threshold| defines a curve, under which FEC
// should be disabled. See below
//
// packet-loss ^ | |
// | | | FEC
// | \ \ ON
// | FEC \ \_______ fec_enabling_threshold
// | OFF \_________ fec_disabling_threshold
// |-----------------> bandwidth
optional Threshold fec_enabling_threshold = 1;
optional Threshold fec_disabling_threshold = 2;
}
message FrameLengthController {
// Uplink packet loss fraction below which frame length can increase.
optional float fl_increasing_packet_loss_fraction = 1;
// Uplink packet loss fraction above which frame length should decrease.
optional float fl_decreasing_packet_loss_fraction = 2;
// Uplink bandwidth below which frame length can switch from 20ms to 60ms.
optional int32 fl_20ms_to_60ms_bandwidth_bps = 3;
// Uplink bandwidth above which frame length should switch from 60ms to 20ms.
optional int32 fl_60ms_to_20ms_bandwidth_bps = 4;
// Uplink bandwidth below which frame length can switch from 60ms to 120ms.
optional int32 fl_60ms_to_120ms_bandwidth_bps = 5;
// Uplink bandwidth above which frame length should switch from 120ms to 60ms.
optional int32 fl_120ms_to_60ms_bandwidth_bps = 6;
// Offset to apply to the per-packet overhead when increasing frame length.
optional int32 fl_increase_overhead_offset = 7;
// Offset to apply to the per-packet overhead when decreasing frame length.
optional int32 fl_decrease_overhead_offset = 8;
// Uplink bandwidth below which frame length can switch from 20ms to 40ms. In
// current implementation, defining this will invalidate
// fl_20ms_to_60ms_bandwidth_bps.
optional int32 fl_20ms_to_40ms_bandwidth_bps = 9;
// Uplink bandwidth above which frame length should switch from 40ms to 20ms.
optional int32 fl_40ms_to_20ms_bandwidth_bps = 10;
// Uplink bandwidth below which frame length can switch from 40ms to 60ms.
optional int32 fl_40ms_to_60ms_bandwidth_bps = 11;
// Uplink bandwidth above which frame length should switch from 60ms to 40ms.
// In current implementation, defining this will invalidate
// fl_60ms_to_20ms_bandwidth_bps.
optional int32 fl_60ms_to_40ms_bandwidth_bps = 12;
}
message ChannelController {
// Uplink bandwidth above which the number of encoded channels should switch
// from 1 to 2.
optional int32 channel_1_to_2_bandwidth_bps = 1;
// Uplink bandwidth below which the number of encoded channels should switch
// from 2 to 1.
optional int32 channel_2_to_1_bandwidth_bps = 2;
}
message DtxController {
// Uplink bandwidth below which DTX should be switched on.
optional int32 dtx_enabling_bandwidth_bps = 1;
// Uplink bandwidth above which DTX should be switched off.
optional int32 dtx_disabling_bandwidth_bps = 2;
}
message BitrateController {
// Offset to apply to per-packet overhead when the frame length is increased.
optional int32 fl_increase_overhead_offset = 1;
// Offset to apply to per-packet overhead when the frame length is decreased.
optional int32 fl_decrease_overhead_offset = 2;
}
message Controller {
message ScoringPoint {
// |ScoringPoint| is a subspace of network condition. It is used for
// comparing the significance of controllers.
optional int32 uplink_bandwidth_bps = 1;
optional float uplink_packet_loss_fraction = 2;
}
// The distance from |scoring_point| to a given network condition defines
// the significance of this controller with respect that network condition.
// Shorter distance means higher significance. The significances of
// controllers determine their order in the processing pipeline. Controllers
// without |scoring_point| follow their default order in
// |ControllerManager::controllers|.
optional ScoringPoint scoring_point = 1;
oneof controller {
FecController fec_controller = 21;
FrameLengthController frame_length_controller = 22;
ChannelController channel_controller = 23;
DtxController dtx_controller = 24;
BitrateController bitrate_controller = 25;
FecControllerRplrBased fec_controller_rplr_based = 26;
}
}
message ControllerManager {
repeated Controller controllers = 1;
// Least time since last reordering for a new reordering to be made.
optional int32 min_reordering_time_ms = 2;
// Least squared distance from last scoring point for a new reordering to be
// made.
optional float min_reordering_squared_distance = 3;
}

View File

@ -0,0 +1,19 @@
/*
* 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/audio_network_adaptor/controller.h"
namespace webrtc {
Controller::NetworkMetrics::NetworkMetrics() = default;
Controller::NetworkMetrics::~NetworkMetrics() = default;
} // namespace webrtc

View File

@ -0,0 +1,42 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_CONTROLLER_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_CONTROLLER_H_
#include "absl/types/optional.h"
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h"
namespace webrtc {
class Controller {
public:
struct NetworkMetrics {
NetworkMetrics();
~NetworkMetrics();
absl::optional<int> uplink_bandwidth_bps;
absl::optional<float> uplink_packet_loss_fraction;
absl::optional<int> target_audio_bitrate_bps;
absl::optional<int> rtt_ms;
absl::optional<size_t> overhead_bytes_per_packet;
};
virtual ~Controller() = default;
// Informs network metrics update to this controller. Any non-empty field
// indicates an update on the corresponding network metric.
virtual void UpdateNetworkMetrics(const NetworkMetrics& network_metrics) = 0;
virtual void MakeDecision(AudioEncoderRuntimeConfig* config) = 0;
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_CONTROLLER_H_

View File

@ -0,0 +1,437 @@
/*
* 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/audio_network_adaptor/controller_manager.h"
#include <cmath>
#include <string>
#include <utility>
#include "modules/audio_coding/audio_network_adaptor/bitrate_controller.h"
#include "modules/audio_coding/audio_network_adaptor/channel_controller.h"
#include "modules/audio_coding/audio_network_adaptor/debug_dump_writer.h"
#include "modules/audio_coding/audio_network_adaptor/dtx_controller.h"
#include "modules/audio_coding/audio_network_adaptor/fec_controller_plr_based.h"
#include "modules/audio_coding/audio_network_adaptor/frame_length_controller.h"
#include "modules/audio_coding/audio_network_adaptor/util/threshold_curve.h"
#include "rtc_base/ignore_wundef.h"
#include "rtc_base/logging.h"
#include "rtc_base/time_utils.h"
#if WEBRTC_ENABLE_PROTOBUF
RTC_PUSH_IGNORING_WUNDEF()
#ifdef WEBRTC_ANDROID_PLATFORM_BUILD
#include "external/webrtc/webrtc/modules/audio_coding/audio_network_adaptor/config.pb.h"
#else
#include "modules/audio_coding/audio_network_adaptor/config.pb.h"
#endif
RTC_POP_IGNORING_WUNDEF()
#endif
namespace webrtc {
namespace {
#if WEBRTC_ENABLE_PROTOBUF
std::unique_ptr<FecControllerPlrBased> CreateFecControllerPlrBased(
const audio_network_adaptor::config::FecController& config,
bool initial_fec_enabled) {
RTC_CHECK(config.has_fec_enabling_threshold());
RTC_CHECK(config.has_fec_disabling_threshold());
RTC_CHECK(config.has_time_constant_ms());
auto& fec_enabling_threshold = config.fec_enabling_threshold();
RTC_CHECK(fec_enabling_threshold.has_low_bandwidth_bps());
RTC_CHECK(fec_enabling_threshold.has_low_bandwidth_packet_loss());
RTC_CHECK(fec_enabling_threshold.has_high_bandwidth_bps());
RTC_CHECK(fec_enabling_threshold.has_high_bandwidth_packet_loss());
auto& fec_disabling_threshold = config.fec_disabling_threshold();
RTC_CHECK(fec_disabling_threshold.has_low_bandwidth_bps());
RTC_CHECK(fec_disabling_threshold.has_low_bandwidth_packet_loss());
RTC_CHECK(fec_disabling_threshold.has_high_bandwidth_bps());
RTC_CHECK(fec_disabling_threshold.has_high_bandwidth_packet_loss());
return std::unique_ptr<FecControllerPlrBased>(
new FecControllerPlrBased(FecControllerPlrBased::Config(
initial_fec_enabled,
ThresholdCurve(fec_enabling_threshold.low_bandwidth_bps(),
fec_enabling_threshold.low_bandwidth_packet_loss(),
fec_enabling_threshold.high_bandwidth_bps(),
fec_enabling_threshold.high_bandwidth_packet_loss()),
ThresholdCurve(fec_disabling_threshold.low_bandwidth_bps(),
fec_disabling_threshold.low_bandwidth_packet_loss(),
fec_disabling_threshold.high_bandwidth_bps(),
fec_disabling_threshold.high_bandwidth_packet_loss()),
config.time_constant_ms())));
}
std::unique_ptr<FrameLengthController> CreateFrameLengthController(
const audio_network_adaptor::config::FrameLengthController& config,
rtc::ArrayView<const int> encoder_frame_lengths_ms,
int initial_frame_length_ms,
int min_encoder_bitrate_bps) {
RTC_CHECK(config.has_fl_increasing_packet_loss_fraction());
RTC_CHECK(config.has_fl_decreasing_packet_loss_fraction());
std::map<FrameLengthController::Config::FrameLengthChange, int>
fl_changing_bandwidths_bps;
if (config.has_fl_20ms_to_60ms_bandwidth_bps()) {
fl_changing_bandwidths_bps.insert(
std::make_pair(FrameLengthController::Config::FrameLengthChange(20, 60),
config.fl_20ms_to_60ms_bandwidth_bps()));
}
if (config.has_fl_60ms_to_20ms_bandwidth_bps()) {
fl_changing_bandwidths_bps.insert(
std::make_pair(FrameLengthController::Config::FrameLengthChange(60, 20),
config.fl_60ms_to_20ms_bandwidth_bps()));
}
if (config.has_fl_20ms_to_40ms_bandwidth_bps()) {
fl_changing_bandwidths_bps.insert(
std::make_pair(FrameLengthController::Config::FrameLengthChange(20, 40),
config.fl_20ms_to_40ms_bandwidth_bps()));
}
if (config.has_fl_40ms_to_20ms_bandwidth_bps()) {
fl_changing_bandwidths_bps.insert(
std::make_pair(FrameLengthController::Config::FrameLengthChange(40, 20),
config.fl_40ms_to_20ms_bandwidth_bps()));
}
if (config.has_fl_40ms_to_60ms_bandwidth_bps()) {
fl_changing_bandwidths_bps.insert(
std::make_pair(FrameLengthController::Config::FrameLengthChange(40, 60),
config.fl_40ms_to_60ms_bandwidth_bps()));
}
if (config.has_fl_60ms_to_40ms_bandwidth_bps()) {
fl_changing_bandwidths_bps.insert(
std::make_pair(FrameLengthController::Config::FrameLengthChange(60, 40),
config.fl_60ms_to_40ms_bandwidth_bps()));
}
if (config.has_fl_60ms_to_120ms_bandwidth_bps()) {
fl_changing_bandwidths_bps.insert(std::make_pair(
FrameLengthController::Config::FrameLengthChange(60, 120),
config.fl_60ms_to_120ms_bandwidth_bps()));
}
if (config.has_fl_120ms_to_60ms_bandwidth_bps()) {
fl_changing_bandwidths_bps.insert(std::make_pair(
FrameLengthController::Config::FrameLengthChange(120, 60),
config.fl_120ms_to_60ms_bandwidth_bps()));
}
int fl_increase_overhead_offset = 0;
if (config.has_fl_increase_overhead_offset()) {
fl_increase_overhead_offset = config.fl_increase_overhead_offset();
}
int fl_decrease_overhead_offset = 0;
if (config.has_fl_decrease_overhead_offset()) {
fl_decrease_overhead_offset = config.fl_decrease_overhead_offset();
}
FrameLengthController::Config ctor_config(
std::set<int>(), initial_frame_length_ms, min_encoder_bitrate_bps,
config.fl_increasing_packet_loss_fraction(),
config.fl_decreasing_packet_loss_fraction(), fl_increase_overhead_offset,
fl_decrease_overhead_offset, std::move(fl_changing_bandwidths_bps));
for (auto frame_length : encoder_frame_lengths_ms)
ctor_config.encoder_frame_lengths_ms.insert(frame_length);
return std::unique_ptr<FrameLengthController>(
new FrameLengthController(ctor_config));
}
std::unique_ptr<ChannelController> CreateChannelController(
const audio_network_adaptor::config::ChannelController& config,
size_t num_encoder_channels,
size_t intial_channels_to_encode) {
RTC_CHECK(config.has_channel_1_to_2_bandwidth_bps());
RTC_CHECK(config.has_channel_2_to_1_bandwidth_bps());
return std::unique_ptr<ChannelController>(new ChannelController(
ChannelController::Config(num_encoder_channels, intial_channels_to_encode,
config.channel_1_to_2_bandwidth_bps(),
config.channel_2_to_1_bandwidth_bps())));
}
std::unique_ptr<DtxController> CreateDtxController(
const audio_network_adaptor::config::DtxController& dtx_config,
bool initial_dtx_enabled) {
RTC_CHECK(dtx_config.has_dtx_enabling_bandwidth_bps());
RTC_CHECK(dtx_config.has_dtx_disabling_bandwidth_bps());
return std::unique_ptr<DtxController>(new DtxController(DtxController::Config(
initial_dtx_enabled, dtx_config.dtx_enabling_bandwidth_bps(),
dtx_config.dtx_disabling_bandwidth_bps())));
}
using audio_network_adaptor::BitrateController;
std::unique_ptr<BitrateController> CreateBitrateController(
const audio_network_adaptor::config::BitrateController& bitrate_config,
int initial_bitrate_bps,
int initial_frame_length_ms) {
int fl_increase_overhead_offset = 0;
if (bitrate_config.has_fl_increase_overhead_offset()) {
fl_increase_overhead_offset = bitrate_config.fl_increase_overhead_offset();
}
int fl_decrease_overhead_offset = 0;
if (bitrate_config.has_fl_decrease_overhead_offset()) {
fl_decrease_overhead_offset = bitrate_config.fl_decrease_overhead_offset();
}
return std::unique_ptr<BitrateController>(
new BitrateController(BitrateController::Config(
initial_bitrate_bps, initial_frame_length_ms,
fl_increase_overhead_offset, fl_decrease_overhead_offset)));
}
#endif // WEBRTC_ENABLE_PROTOBUF
} // namespace
ControllerManagerImpl::Config::Config(int min_reordering_time_ms,
float min_reordering_squared_distance)
: min_reordering_time_ms(min_reordering_time_ms),
min_reordering_squared_distance(min_reordering_squared_distance) {}
ControllerManagerImpl::Config::~Config() = default;
std::unique_ptr<ControllerManager> ControllerManagerImpl::Create(
const std::string& config_string,
size_t num_encoder_channels,
rtc::ArrayView<const int> encoder_frame_lengths_ms,
int min_encoder_bitrate_bps,
size_t intial_channels_to_encode,
int initial_frame_length_ms,
int initial_bitrate_bps,
bool initial_fec_enabled,
bool initial_dtx_enabled) {
return Create(config_string, num_encoder_channels, encoder_frame_lengths_ms,
min_encoder_bitrate_bps, intial_channels_to_encode,
initial_frame_length_ms, initial_bitrate_bps,
initial_fec_enabled, initial_dtx_enabled, nullptr);
}
std::unique_ptr<ControllerManager> ControllerManagerImpl::Create(
const std::string& config_string,
size_t num_encoder_channels,
rtc::ArrayView<const int> encoder_frame_lengths_ms,
int min_encoder_bitrate_bps,
size_t intial_channels_to_encode,
int initial_frame_length_ms,
int initial_bitrate_bps,
bool initial_fec_enabled,
bool initial_dtx_enabled,
DebugDumpWriter* debug_dump_writer) {
#if WEBRTC_ENABLE_PROTOBUF
audio_network_adaptor::config::ControllerManager controller_manager_config;
RTC_CHECK(controller_manager_config.ParseFromString(config_string));
if (debug_dump_writer)
debug_dump_writer->DumpControllerManagerConfig(controller_manager_config,
rtc::TimeMillis());
std::vector<std::unique_ptr<Controller>> controllers;
std::map<const Controller*, std::pair<int, float>> scoring_points;
for (int i = 0; i < controller_manager_config.controllers_size(); ++i) {
auto& controller_config = controller_manager_config.controllers(i);
std::unique_ptr<Controller> controller;
switch (controller_config.controller_case()) {
case audio_network_adaptor::config::Controller::kFecController:
controller = CreateFecControllerPlrBased(
controller_config.fec_controller(), initial_fec_enabled);
break;
case audio_network_adaptor::config::Controller::kFecControllerRplrBased:
// FecControllerRplrBased has been removed and can't be used anymore.
RTC_NOTREACHED();
continue;
case audio_network_adaptor::config::Controller::kFrameLengthController:
controller = CreateFrameLengthController(
controller_config.frame_length_controller(),
encoder_frame_lengths_ms, initial_frame_length_ms,
min_encoder_bitrate_bps);
break;
case audio_network_adaptor::config::Controller::kChannelController:
controller = CreateChannelController(
controller_config.channel_controller(), num_encoder_channels,
intial_channels_to_encode);
break;
case audio_network_adaptor::config::Controller::kDtxController:
controller = CreateDtxController(controller_config.dtx_controller(),
initial_dtx_enabled);
break;
case audio_network_adaptor::config::Controller::kBitrateController:
controller = CreateBitrateController(
controller_config.bitrate_controller(), initial_bitrate_bps,
initial_frame_length_ms);
break;
default:
RTC_NOTREACHED();
}
if (controller_config.has_scoring_point()) {
auto& scoring_point = controller_config.scoring_point();
RTC_CHECK(scoring_point.has_uplink_bandwidth_bps());
RTC_CHECK(scoring_point.has_uplink_packet_loss_fraction());
scoring_points[controller.get()] = std::make_pair<int, float>(
scoring_point.uplink_bandwidth_bps(),
scoring_point.uplink_packet_loss_fraction());
}
controllers.push_back(std::move(controller));
}
if (scoring_points.size() == 0) {
return std::unique_ptr<ControllerManagerImpl>(
new ControllerManagerImpl(ControllerManagerImpl::Config(0, 0),
std::move(controllers), scoring_points));
} else {
RTC_CHECK(controller_manager_config.has_min_reordering_time_ms());
RTC_CHECK(controller_manager_config.has_min_reordering_squared_distance());
return std::unique_ptr<ControllerManagerImpl>(new ControllerManagerImpl(
ControllerManagerImpl::Config(
controller_manager_config.min_reordering_time_ms(),
controller_manager_config.min_reordering_squared_distance()),
std::move(controllers), scoring_points));
}
#else
RTC_NOTREACHED();
return nullptr;
#endif // WEBRTC_ENABLE_PROTOBUF
}
ControllerManagerImpl::ControllerManagerImpl(const Config& config)
: ControllerManagerImpl(
config,
std::vector<std::unique_ptr<Controller>>(),
std::map<const Controller*, std::pair<int, float>>()) {}
ControllerManagerImpl::ControllerManagerImpl(
const Config& config,
std::vector<std::unique_ptr<Controller>> controllers,
const std::map<const Controller*, std::pair<int, float>>& scoring_points)
: config_(config),
controllers_(std::move(controllers)),
last_reordering_time_ms_(absl::nullopt),
last_scoring_point_(0, 0.0) {
for (auto& controller : controllers_)
default_sorted_controllers_.push_back(controller.get());
sorted_controllers_ = default_sorted_controllers_;
for (auto& controller_point : scoring_points) {
controller_scoring_points_.insert(std::make_pair(
controller_point.first, ScoringPoint(controller_point.second.first,
controller_point.second.second)));
}
}
ControllerManagerImpl::~ControllerManagerImpl() = default;
std::vector<Controller*> ControllerManagerImpl::GetSortedControllers(
const Controller::NetworkMetrics& metrics) {
if (controller_scoring_points_.size() == 0)
return default_sorted_controllers_;
if (!metrics.uplink_bandwidth_bps || !metrics.uplink_packet_loss_fraction)
return sorted_controllers_;
const int64_t now_ms = rtc::TimeMillis();
if (last_reordering_time_ms_ &&
now_ms - *last_reordering_time_ms_ < config_.min_reordering_time_ms)
return sorted_controllers_;
ScoringPoint scoring_point(*metrics.uplink_bandwidth_bps,
*metrics.uplink_packet_loss_fraction);
if (last_reordering_time_ms_ &&
last_scoring_point_.SquaredDistanceTo(scoring_point) <
config_.min_reordering_squared_distance)
return sorted_controllers_;
// Sort controllers according to the distances of |scoring_point| to the
// scoring points of controllers.
//
// A controller that does not associate with any scoring point
// are treated as if
// 1) they are less important than any controller that has a scoring point,
// 2) they are equally important to any controller that has no scoring point,
// and their relative order will follow |default_sorted_controllers_|.
std::vector<Controller*> sorted_controllers(default_sorted_controllers_);
std::stable_sort(
sorted_controllers.begin(), sorted_controllers.end(),
[this, &scoring_point](const Controller* lhs, const Controller* rhs) {
auto lhs_scoring_point = controller_scoring_points_.find(lhs);
auto rhs_scoring_point = controller_scoring_points_.find(rhs);
if (lhs_scoring_point == controller_scoring_points_.end())
return false;
if (rhs_scoring_point == controller_scoring_points_.end())
return true;
return lhs_scoring_point->second.SquaredDistanceTo(scoring_point) <
rhs_scoring_point->second.SquaredDistanceTo(scoring_point);
});
if (sorted_controllers_ != sorted_controllers) {
sorted_controllers_ = sorted_controllers;
last_reordering_time_ms_ = now_ms;
last_scoring_point_ = scoring_point;
}
return sorted_controllers_;
}
std::vector<Controller*> ControllerManagerImpl::GetControllers() const {
return default_sorted_controllers_;
}
ControllerManagerImpl::ScoringPoint::ScoringPoint(
int uplink_bandwidth_bps,
float uplink_packet_loss_fraction)
: uplink_bandwidth_bps(uplink_bandwidth_bps),
uplink_packet_loss_fraction(uplink_packet_loss_fraction) {}
namespace {
constexpr int kMinUplinkBandwidthBps = 0;
constexpr int kMaxUplinkBandwidthBps = 120000;
float NormalizeUplinkBandwidth(int uplink_bandwidth_bps) {
uplink_bandwidth_bps =
std::min(kMaxUplinkBandwidthBps,
std::max(kMinUplinkBandwidthBps, uplink_bandwidth_bps));
return static_cast<float>(uplink_bandwidth_bps - kMinUplinkBandwidthBps) /
(kMaxUplinkBandwidthBps - kMinUplinkBandwidthBps);
}
float NormalizePacketLossFraction(float uplink_packet_loss_fraction) {
// |uplink_packet_loss_fraction| is seldom larger than 0.3, so we scale it up
// by 3.3333f.
return std::min(uplink_packet_loss_fraction * 3.3333f, 1.0f);
}
} // namespace
float ControllerManagerImpl::ScoringPoint::SquaredDistanceTo(
const ScoringPoint& scoring_point) const {
float diff_normalized_bitrate_bps =
NormalizeUplinkBandwidth(scoring_point.uplink_bandwidth_bps) -
NormalizeUplinkBandwidth(uplink_bandwidth_bps);
float diff_normalized_packet_loss =
NormalizePacketLossFraction(scoring_point.uplink_packet_loss_fraction) -
NormalizePacketLossFraction(uplink_packet_loss_fraction);
return std::pow(diff_normalized_bitrate_bps, 2) +
std::pow(diff_normalized_packet_loss, 2);
}
} // namespace webrtc

View File

@ -0,0 +1,123 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_CONTROLLER_MANAGER_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_CONTROLLER_MANAGER_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "modules/audio_coding/audio_network_adaptor/controller.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
class DebugDumpWriter;
class ControllerManager {
public:
virtual ~ControllerManager() = default;
// Sort controllers based on their significance.
virtual std::vector<Controller*> GetSortedControllers(
const Controller::NetworkMetrics& metrics) = 0;
virtual std::vector<Controller*> GetControllers() const = 0;
};
class ControllerManagerImpl final : public ControllerManager {
public:
struct Config {
Config(int min_reordering_time_ms, float min_reordering_squared_distance);
~Config();
// Least time since last reordering for a new reordering to be made.
int min_reordering_time_ms;
// Least squared distance from last scoring point for a new reordering to be
// made.
float min_reordering_squared_distance;
};
static std::unique_ptr<ControllerManager> Create(
const std::string& config_string,
size_t num_encoder_channels,
rtc::ArrayView<const int> encoder_frame_lengths_ms,
int min_encoder_bitrate_bps,
size_t intial_channels_to_encode,
int initial_frame_length_ms,
int initial_bitrate_bps,
bool initial_fec_enabled,
bool initial_dtx_enabled);
static std::unique_ptr<ControllerManager> Create(
const std::string& config_string,
size_t num_encoder_channels,
rtc::ArrayView<const int> encoder_frame_lengths_ms,
int min_encoder_bitrate_bps,
size_t intial_channels_to_encode,
int initial_frame_length_ms,
int initial_bitrate_bps,
bool initial_fec_enabled,
bool initial_dtx_enabled,
DebugDumpWriter* debug_dump_writer);
explicit ControllerManagerImpl(const Config& config);
// Dependency injection for testing.
ControllerManagerImpl(
const Config& config,
std::vector<std::unique_ptr<Controller>> controllers,
const std::map<const Controller*, std::pair<int, float>>&
chracteristic_points);
~ControllerManagerImpl() override;
// Sort controllers based on their significance.
std::vector<Controller*> GetSortedControllers(
const Controller::NetworkMetrics& metrics) override;
std::vector<Controller*> GetControllers() const override;
private:
// Scoring point is a subset of NetworkMetrics that is used for comparing the
// significance of controllers.
struct ScoringPoint {
// TODO(eladalon): Do we want to experiment with RPLR-based scoring?
ScoringPoint(int uplink_bandwidth_bps, float uplink_packet_loss_fraction);
// Calculate the normalized [0,1] distance between two scoring points.
float SquaredDistanceTo(const ScoringPoint& scoring_point) const;
int uplink_bandwidth_bps;
float uplink_packet_loss_fraction;
};
const Config config_;
std::vector<std::unique_ptr<Controller>> controllers_;
absl::optional<int64_t> last_reordering_time_ms_;
ScoringPoint last_scoring_point_;
std::vector<Controller*> default_sorted_controllers_;
std::vector<Controller*> sorted_controllers_;
// |scoring_points_| saves the scoring points of various
// controllers.
std::map<const Controller*, ScoringPoint> controller_scoring_points_;
RTC_DISALLOW_COPY_AND_ASSIGN(ControllerManagerImpl);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_CONTROLLER_MANAGER_H_

View File

@ -0,0 +1,469 @@
/*
* 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/audio_network_adaptor/controller_manager.h"
#include <string>
#include <utility>
#include "modules/audio_coding/audio_network_adaptor/mock/mock_controller.h"
#include "modules/audio_coding/audio_network_adaptor/mock/mock_debug_dump_writer.h"
#include "rtc_base/fake_clock.h"
#include "rtc_base/ignore_wundef.h"
#include "test/gtest.h"
#if WEBRTC_ENABLE_PROTOBUF
RTC_PUSH_IGNORING_WUNDEF()
#ifdef WEBRTC_ANDROID_PLATFORM_BUILD
#include "external/webrtc/webrtc/modules/audio_coding/audio_network_adaptor/config.pb.h"
#else
#include "modules/audio_coding/audio_network_adaptor/config.pb.h"
#endif
RTC_POP_IGNORING_WUNDEF()
#endif
namespace webrtc {
using ::testing::_;
using ::testing::NiceMock;
namespace {
constexpr size_t kNumControllers = 4;
constexpr int kChracteristicBandwithBps[2] = {15000, 0};
constexpr float kChracteristicPacketLossFraction[2] = {0.2f, 0.0f};
constexpr int kMinReorderingTimeMs = 200;
constexpr int kFactor = 100;
constexpr float kMinReorderingSquareDistance = 1.0f / kFactor / kFactor;
// |kMinUplinkBandwidthBps| and |kMaxUplinkBandwidthBps| are copied from
// controller_manager.cc
constexpr int kMinUplinkBandwidthBps = 0;
constexpr int kMaxUplinkBandwidthBps = 120000;
constexpr int kMinBandwithChangeBps =
(kMaxUplinkBandwidthBps - kMinUplinkBandwidthBps) / kFactor;
struct ControllerManagerStates {
std::unique_ptr<ControllerManager> controller_manager;
std::vector<MockController*> mock_controllers;
};
ControllerManagerStates CreateControllerManager() {
ControllerManagerStates states;
std::vector<std::unique_ptr<Controller>> controllers;
std::map<const Controller*, std::pair<int, float>> chracteristic_points;
for (size_t i = 0; i < kNumControllers; ++i) {
auto controller =
std::unique_ptr<MockController>(new NiceMock<MockController>());
EXPECT_CALL(*controller, Die());
states.mock_controllers.push_back(controller.get());
controllers.push_back(std::move(controller));
}
// Assign characteristic points to the last two controllers.
chracteristic_points[states.mock_controllers[kNumControllers - 2]] =
std::make_pair(kChracteristicBandwithBps[0],
kChracteristicPacketLossFraction[0]);
chracteristic_points[states.mock_controllers[kNumControllers - 1]] =
std::make_pair(kChracteristicBandwithBps[1],
kChracteristicPacketLossFraction[1]);
states.controller_manager.reset(new ControllerManagerImpl(
ControllerManagerImpl::Config(kMinReorderingTimeMs,
kMinReorderingSquareDistance),
std::move(controllers), chracteristic_points));
return states;
}
// |expected_order| contains the expected indices of all controllers in the
// vector of controllers returned by GetSortedControllers(). A negative index
// means that we do not care about its exact place, but we do check that it
// exists in the vector.
void CheckControllersOrder(
ControllerManagerStates* states,
const absl::optional<int>& uplink_bandwidth_bps,
const absl::optional<float>& uplink_packet_loss_fraction,
const std::vector<int>& expected_order) {
RTC_DCHECK_EQ(kNumControllers, expected_order.size());
Controller::NetworkMetrics metrics;
metrics.uplink_bandwidth_bps = uplink_bandwidth_bps;
metrics.uplink_packet_loss_fraction = uplink_packet_loss_fraction;
auto check = states->controller_manager->GetSortedControllers(metrics);
EXPECT_EQ(states->mock_controllers.size(), check.size());
for (size_t i = 0; i < states->mock_controllers.size(); ++i) {
if (expected_order[i] >= 0) {
EXPECT_EQ(states->mock_controllers[i], check[expected_order[i]]);
} else {
EXPECT_NE(check.end(), std::find(check.begin(), check.end(),
states->mock_controllers[i]));
}
}
}
} // namespace
TEST(ControllerManagerTest, GetControllersReturnAllControllers) {
auto states = CreateControllerManager();
auto check = states.controller_manager->GetControllers();
// Verify that controllers in |check| are one-to-one mapped to those in
// |mock_controllers_|.
EXPECT_EQ(states.mock_controllers.size(), check.size());
for (auto& controller : check)
EXPECT_NE(states.mock_controllers.end(),
std::find(states.mock_controllers.begin(),
states.mock_controllers.end(), controller));
}
TEST(ControllerManagerTest, ControllersInDefaultOrderOnEmptyNetworkMetrics) {
auto states = CreateControllerManager();
// |network_metrics| are empty, and the controllers are supposed to follow the
// default order.
CheckControllersOrder(&states, absl::nullopt, absl::nullopt, {0, 1, 2, 3});
}
TEST(ControllerManagerTest, ControllersWithoutCharPointAtEndAndInDefaultOrder) {
auto states = CreateControllerManager();
CheckControllersOrder(&states, 0, 0.0,
{kNumControllers - 2, kNumControllers - 1, -1, -1});
}
TEST(ControllerManagerTest, ControllersWithCharPointDependOnNetworkMetrics) {
auto states = CreateControllerManager();
CheckControllersOrder(&states, kChracteristicBandwithBps[1],
kChracteristicPacketLossFraction[1],
{kNumControllers - 2, kNumControllers - 1, 1, 0});
}
TEST(ControllerManagerTest, DoNotReorderBeforeMinReordingTime) {
rtc::ScopedFakeClock fake_clock;
auto states = CreateControllerManager();
CheckControllersOrder(&states, kChracteristicBandwithBps[0],
kChracteristicPacketLossFraction[0],
{kNumControllers - 2, kNumControllers - 1, 0, 1});
fake_clock.AdvanceTime(TimeDelta::Millis(kMinReorderingTimeMs - 1));
// Move uplink bandwidth and packet loss fraction to the other controller's
// characteristic point, which would cause controller manager to reorder the
// controllers if time had reached min reordering time.
CheckControllersOrder(&states, kChracteristicBandwithBps[1],
kChracteristicPacketLossFraction[1],
{kNumControllers - 2, kNumControllers - 1, 0, 1});
}
TEST(ControllerManagerTest, ReorderBeyondMinReordingTimeAndMinDistance) {
rtc::ScopedFakeClock fake_clock;
auto states = CreateControllerManager();
constexpr int kBandwidthBps =
(kChracteristicBandwithBps[0] + kChracteristicBandwithBps[1]) / 2;
constexpr float kPacketLossFraction = (kChracteristicPacketLossFraction[0] +
kChracteristicPacketLossFraction[1]) /
2.0f;
// Set network metrics to be in the middle between the characteristic points
// of two controllers.
CheckControllersOrder(&states, kBandwidthBps, kPacketLossFraction,
{kNumControllers - 2, kNumControllers - 1, 0, 1});
fake_clock.AdvanceTime(TimeDelta::Millis(kMinReorderingTimeMs));
// Then let network metrics move a little towards the other controller.
CheckControllersOrder(&states, kBandwidthBps - kMinBandwithChangeBps - 1,
kPacketLossFraction,
{kNumControllers - 2, kNumControllers - 1, 1, 0});
}
TEST(ControllerManagerTest, DoNotReorderIfNetworkMetricsChangeTooSmall) {
rtc::ScopedFakeClock fake_clock;
auto states = CreateControllerManager();
constexpr int kBandwidthBps =
(kChracteristicBandwithBps[0] + kChracteristicBandwithBps[1]) / 2;
constexpr float kPacketLossFraction = (kChracteristicPacketLossFraction[0] +
kChracteristicPacketLossFraction[1]) /
2.0f;
// Set network metrics to be in the middle between the characteristic points
// of two controllers.
CheckControllersOrder(&states, kBandwidthBps, kPacketLossFraction,
{kNumControllers - 2, kNumControllers - 1, 0, 1});
fake_clock.AdvanceTime(TimeDelta::Millis(kMinReorderingTimeMs));
// Then let network metrics move a little towards the other controller.
CheckControllersOrder(&states, kBandwidthBps - kMinBandwithChangeBps + 1,
kPacketLossFraction,
{kNumControllers - 2, kNumControllers - 1, 0, 1});
}
#if WEBRTC_ENABLE_PROTOBUF
namespace {
void AddBitrateControllerConfig(
audio_network_adaptor::config::ControllerManager* config) {
config->add_controllers()->mutable_bitrate_controller();
}
void AddChannelControllerConfig(
audio_network_adaptor::config::ControllerManager* config) {
auto controller_config =
config->add_controllers()->mutable_channel_controller();
controller_config->set_channel_1_to_2_bandwidth_bps(31000);
controller_config->set_channel_2_to_1_bandwidth_bps(29000);
}
void AddDtxControllerConfig(
audio_network_adaptor::config::ControllerManager* config) {
auto controller_config = config->add_controllers()->mutable_dtx_controller();
controller_config->set_dtx_enabling_bandwidth_bps(55000);
controller_config->set_dtx_disabling_bandwidth_bps(65000);
}
void AddFecControllerConfig(
audio_network_adaptor::config::ControllerManager* config) {
auto controller_config_ext = config->add_controllers();
auto controller_config = controller_config_ext->mutable_fec_controller();
auto fec_enabling_threshold =
controller_config->mutable_fec_enabling_threshold();
fec_enabling_threshold->set_low_bandwidth_bps(17000);
fec_enabling_threshold->set_low_bandwidth_packet_loss(0.1f);
fec_enabling_threshold->set_high_bandwidth_bps(64000);
fec_enabling_threshold->set_high_bandwidth_packet_loss(0.05f);
auto fec_disabling_threshold =
controller_config->mutable_fec_disabling_threshold();
fec_disabling_threshold->set_low_bandwidth_bps(15000);
fec_disabling_threshold->set_low_bandwidth_packet_loss(0.08f);
fec_disabling_threshold->set_high_bandwidth_bps(64000);
fec_disabling_threshold->set_high_bandwidth_packet_loss(0.01f);
controller_config->set_time_constant_ms(500);
auto scoring_point = controller_config_ext->mutable_scoring_point();
scoring_point->set_uplink_bandwidth_bps(kChracteristicBandwithBps[0]);
scoring_point->set_uplink_packet_loss_fraction(
kChracteristicPacketLossFraction[0]);
}
void AddFrameLengthControllerConfig(
audio_network_adaptor::config::ControllerManager* config) {
auto controller_config_ext = config->add_controllers();
auto controller_config =
controller_config_ext->mutable_frame_length_controller();
controller_config->set_fl_decreasing_packet_loss_fraction(0.05f);
controller_config->set_fl_increasing_packet_loss_fraction(0.04f);
controller_config->set_fl_20ms_to_40ms_bandwidth_bps(80000);
controller_config->set_fl_40ms_to_20ms_bandwidth_bps(88000);
controller_config->set_fl_40ms_to_60ms_bandwidth_bps(72000);
controller_config->set_fl_60ms_to_40ms_bandwidth_bps(80000);
auto scoring_point = controller_config_ext->mutable_scoring_point();
scoring_point->set_uplink_bandwidth_bps(kChracteristicBandwithBps[1]);
scoring_point->set_uplink_packet_loss_fraction(
kChracteristicPacketLossFraction[1]);
}
constexpr int kInitialBitrateBps = 24000;
constexpr size_t kIntialChannelsToEncode = 1;
constexpr bool kInitialDtxEnabled = true;
constexpr bool kInitialFecEnabled = true;
constexpr int kInitialFrameLengthMs = 60;
constexpr int kMinBitrateBps = 6000;
ControllerManagerStates CreateControllerManager(
const std::string& config_string) {
ControllerManagerStates states;
constexpr size_t kNumEncoderChannels = 2;
const std::vector<int> encoder_frame_lengths_ms = {20, 60};
states.controller_manager = ControllerManagerImpl::Create(
config_string, kNumEncoderChannels, encoder_frame_lengths_ms,
kMinBitrateBps, kIntialChannelsToEncode, kInitialFrameLengthMs,
kInitialBitrateBps, kInitialFecEnabled, kInitialDtxEnabled);
return states;
}
enum class ControllerType : int8_t {
FEC,
CHANNEL,
DTX,
FRAME_LENGTH,
BIT_RATE
};
void CheckControllersOrder(const std::vector<Controller*>& controllers,
const std::vector<ControllerType>& expected_types) {
ASSERT_EQ(expected_types.size(), controllers.size());
// We also check that the controllers follow the initial settings.
AudioEncoderRuntimeConfig encoder_config;
for (size_t i = 0; i < controllers.size(); ++i) {
AudioEncoderRuntimeConfig encoder_config;
// We check the order of |controllers| by judging their decisions.
controllers[i]->MakeDecision(&encoder_config);
// Since controllers are not provided with network metrics, they give the
// initial values.
switch (expected_types[i]) {
case ControllerType::FEC:
EXPECT_EQ(kInitialFecEnabled, encoder_config.enable_fec);
break;
case ControllerType::CHANNEL:
EXPECT_EQ(kIntialChannelsToEncode, encoder_config.num_channels);
break;
case ControllerType::DTX:
EXPECT_EQ(kInitialDtxEnabled, encoder_config.enable_dtx);
break;
case ControllerType::FRAME_LENGTH:
EXPECT_EQ(kInitialFrameLengthMs, encoder_config.frame_length_ms);
break;
case ControllerType::BIT_RATE:
EXPECT_EQ(kInitialBitrateBps, encoder_config.bitrate_bps);
}
}
}
MATCHER_P(ControllerManagerEqual, value, "") {
std::string value_string;
std::string arg_string;
EXPECT_TRUE(arg.SerializeToString(&arg_string));
EXPECT_TRUE(value.SerializeToString(&value_string));
return arg_string == value_string;
}
} // namespace
TEST(ControllerManagerTest, DebugDumpLoggedWhenCreateFromConfigString) {
audio_network_adaptor::config::ControllerManager config;
config.set_min_reordering_time_ms(kMinReorderingTimeMs);
config.set_min_reordering_squared_distance(kMinReorderingSquareDistance);
AddFecControllerConfig(&config);
AddChannelControllerConfig(&config);
AddDtxControllerConfig(&config);
AddFrameLengthControllerConfig(&config);
AddBitrateControllerConfig(&config);
std::string config_string;
config.SerializeToString(&config_string);
constexpr size_t kNumEncoderChannels = 2;
const std::vector<int> encoder_frame_lengths_ms = {20, 60};
constexpr int64_t kClockInitialTimeMs = 12345678;
rtc::ScopedFakeClock fake_clock;
fake_clock.AdvanceTime(TimeDelta::Millis(kClockInitialTimeMs));
auto debug_dump_writer =
std::unique_ptr<MockDebugDumpWriter>(new NiceMock<MockDebugDumpWriter>());
EXPECT_CALL(*debug_dump_writer, Die());
EXPECT_CALL(*debug_dump_writer,
DumpControllerManagerConfig(ControllerManagerEqual(config),
kClockInitialTimeMs));
ControllerManagerImpl::Create(config_string, kNumEncoderChannels,
encoder_frame_lengths_ms, kMinBitrateBps,
kIntialChannelsToEncode, kInitialFrameLengthMs,
kInitialBitrateBps, kInitialFecEnabled,
kInitialDtxEnabled, debug_dump_writer.get());
}
TEST(ControllerManagerTest, CreateFromConfigStringAndCheckDefaultOrder) {
audio_network_adaptor::config::ControllerManager config;
config.set_min_reordering_time_ms(kMinReorderingTimeMs);
config.set_min_reordering_squared_distance(kMinReorderingSquareDistance);
AddFecControllerConfig(&config);
AddChannelControllerConfig(&config);
AddDtxControllerConfig(&config);
AddFrameLengthControllerConfig(&config);
AddBitrateControllerConfig(&config);
std::string config_string;
config.SerializeToString(&config_string);
auto states = CreateControllerManager(config_string);
Controller::NetworkMetrics metrics;
auto controllers = states.controller_manager->GetSortedControllers(metrics);
CheckControllersOrder(
controllers,
std::vector<ControllerType>{
ControllerType::FEC, ControllerType::CHANNEL, ControllerType::DTX,
ControllerType::FRAME_LENGTH, ControllerType::BIT_RATE});
}
TEST(ControllerManagerTest, CreateCharPointFreeConfigAndCheckDefaultOrder) {
audio_network_adaptor::config::ControllerManager config;
// Following controllers have no characteristic points.
AddChannelControllerConfig(&config);
AddDtxControllerConfig(&config);
AddBitrateControllerConfig(&config);
std::string config_string;
config.SerializeToString(&config_string);
auto states = CreateControllerManager(config_string);
Controller::NetworkMetrics metrics;
auto controllers = states.controller_manager->GetSortedControllers(metrics);
CheckControllersOrder(
controllers,
std::vector<ControllerType>{ControllerType::CHANNEL, ControllerType::DTX,
ControllerType::BIT_RATE});
}
TEST(ControllerManagerTest, CreateFromConfigStringAndCheckReordering) {
rtc::ScopedFakeClock fake_clock;
audio_network_adaptor::config::ControllerManager config;
config.set_min_reordering_time_ms(kMinReorderingTimeMs);
config.set_min_reordering_squared_distance(kMinReorderingSquareDistance);
AddChannelControllerConfig(&config);
// Internally associated with characteristic point 0.
AddFecControllerConfig(&config);
AddDtxControllerConfig(&config);
// Internally associated with characteristic point 1.
AddFrameLengthControllerConfig(&config);
AddBitrateControllerConfig(&config);
std::string config_string;
config.SerializeToString(&config_string);
auto states = CreateControllerManager(config_string);
Controller::NetworkMetrics metrics;
metrics.uplink_bandwidth_bps = kChracteristicBandwithBps[0];
metrics.uplink_packet_loss_fraction = kChracteristicPacketLossFraction[0];
auto controllers = states.controller_manager->GetSortedControllers(metrics);
CheckControllersOrder(controllers,
std::vector<ControllerType>{
ControllerType::FEC, ControllerType::FRAME_LENGTH,
ControllerType::CHANNEL, ControllerType::DTX,
ControllerType::BIT_RATE});
metrics.uplink_bandwidth_bps = kChracteristicBandwithBps[1];
metrics.uplink_packet_loss_fraction = kChracteristicPacketLossFraction[1];
fake_clock.AdvanceTime(TimeDelta::Millis(kMinReorderingTimeMs - 1));
controllers = states.controller_manager->GetSortedControllers(metrics);
// Should not reorder since min reordering time is not met.
CheckControllersOrder(controllers,
std::vector<ControllerType>{
ControllerType::FEC, ControllerType::FRAME_LENGTH,
ControllerType::CHANNEL, ControllerType::DTX,
ControllerType::BIT_RATE});
fake_clock.AdvanceTime(TimeDelta::Millis(1));
controllers = states.controller_manager->GetSortedControllers(metrics);
// Reorder now.
CheckControllersOrder(controllers,
std::vector<ControllerType>{
ControllerType::FRAME_LENGTH, ControllerType::FEC,
ControllerType::CHANNEL, ControllerType::DTX,
ControllerType::BIT_RATE});
}
#endif // WEBRTC_ENABLE_PROTOBUF
} // namespace webrtc

View File

@ -0,0 +1,42 @@
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package webrtc.audio_network_adaptor.debug_dump;
import "config.proto";
message NetworkMetrics {
optional int32 uplink_bandwidth_bps = 1;
optional float uplink_packet_loss_fraction = 2;
optional int32 target_audio_bitrate_bps = 3;
optional int32 rtt_ms = 4;
optional int32 uplink_recoverable_packet_loss_fraction = 5;
}
message EncoderRuntimeConfig {
optional int32 bitrate_bps = 1;
optional int32 frame_length_ms = 2;
// Note: This is what we tell the encoder. It doesn't have to reflect
// the actual NetworkMetrics; it's subject to our decision.
optional float uplink_packet_loss_fraction = 3;
optional bool enable_fec = 4;
optional bool enable_dtx = 5;
// Some encoders can encode fewer channels than the actual input to make
// better use of the bandwidth. |num_channels| sets the number of channels
// to encode.
optional uint32 num_channels = 6;
}
message Event {
enum Type {
NETWORK_METRICS = 0;
ENCODER_RUNTIME_CONFIG = 1;
CONTROLLER_MANAGER_CONFIG = 2;
}
required Type type = 1;
required uint32 timestamp = 2;
optional NetworkMetrics network_metrics = 3;
optional EncoderRuntimeConfig encoder_runtime_config = 4;
optional webrtc.audio_network_adaptor.config.ControllerManager
controller_manager_config = 5;
}

View File

@ -0,0 +1,163 @@
/*
* 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/audio_network_adaptor/debug_dump_writer.h"
#include <string>
#include "absl/types/optional.h"
#include "rtc_base/checks.h"
#include "rtc_base/ignore_wundef.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/system/file_wrapper.h"
#if WEBRTC_ENABLE_PROTOBUF
RTC_PUSH_IGNORING_WUNDEF()
#ifdef WEBRTC_ANDROID_PLATFORM_BUILD
#include "external/webrtc/webrtc/modules/audio_coding/audio_network_adaptor/debug_dump.pb.h"
#else
#include "modules/audio_coding/audio_network_adaptor/debug_dump.pb.h"
#endif
RTC_POP_IGNORING_WUNDEF()
#endif
namespace webrtc {
#if WEBRTC_ENABLE_PROTOBUF
namespace {
using audio_network_adaptor::debug_dump::EncoderRuntimeConfig;
using audio_network_adaptor::debug_dump::Event;
using audio_network_adaptor::debug_dump::NetworkMetrics;
void DumpEventToFile(const Event& event, FileWrapper* dump_file) {
RTC_CHECK(dump_file->is_open());
std::string dump_data;
event.SerializeToString(&dump_data);
int32_t size = rtc::checked_cast<int32_t>(event.ByteSizeLong());
dump_file->Write(&size, sizeof(size));
dump_file->Write(dump_data.data(), dump_data.length());
}
} // namespace
#endif // WEBRTC_ENABLE_PROTOBUF
class DebugDumpWriterImpl final : public DebugDumpWriter {
public:
explicit DebugDumpWriterImpl(FILE* file_handle);
~DebugDumpWriterImpl() override = default;
void DumpEncoderRuntimeConfig(const AudioEncoderRuntimeConfig& config,
int64_t timestamp) override;
void DumpNetworkMetrics(const Controller::NetworkMetrics& metrics,
int64_t timestamp) override;
#if WEBRTC_ENABLE_PROTOBUF
void DumpControllerManagerConfig(
const audio_network_adaptor::config::ControllerManager&
controller_manager_config,
int64_t timestamp) override;
#endif
private:
FileWrapper dump_file_;
};
DebugDumpWriterImpl::DebugDumpWriterImpl(FILE* file_handle) {
#if WEBRTC_ENABLE_PROTOBUF
dump_file_ = FileWrapper(file_handle);
RTC_CHECK(dump_file_.is_open());
#else
RTC_NOTREACHED();
#endif
}
void DebugDumpWriterImpl::DumpNetworkMetrics(
const Controller::NetworkMetrics& metrics,
int64_t timestamp) {
#if WEBRTC_ENABLE_PROTOBUF
Event event;
event.set_timestamp(timestamp);
event.set_type(Event::NETWORK_METRICS);
auto dump_metrics = event.mutable_network_metrics();
if (metrics.uplink_bandwidth_bps)
dump_metrics->set_uplink_bandwidth_bps(*metrics.uplink_bandwidth_bps);
if (metrics.uplink_packet_loss_fraction) {
dump_metrics->set_uplink_packet_loss_fraction(
*metrics.uplink_packet_loss_fraction);
}
if (metrics.target_audio_bitrate_bps) {
dump_metrics->set_target_audio_bitrate_bps(
*metrics.target_audio_bitrate_bps);
}
if (metrics.rtt_ms)
dump_metrics->set_rtt_ms(*metrics.rtt_ms);
DumpEventToFile(event, &dump_file_);
#endif // WEBRTC_ENABLE_PROTOBUF
}
void DebugDumpWriterImpl::DumpEncoderRuntimeConfig(
const AudioEncoderRuntimeConfig& config,
int64_t timestamp) {
#if WEBRTC_ENABLE_PROTOBUF
Event event;
event.set_timestamp(timestamp);
event.set_type(Event::ENCODER_RUNTIME_CONFIG);
auto dump_config = event.mutable_encoder_runtime_config();
if (config.bitrate_bps)
dump_config->set_bitrate_bps(*config.bitrate_bps);
if (config.frame_length_ms)
dump_config->set_frame_length_ms(*config.frame_length_ms);
if (config.uplink_packet_loss_fraction) {
dump_config->set_uplink_packet_loss_fraction(
*config.uplink_packet_loss_fraction);
}
if (config.enable_fec)
dump_config->set_enable_fec(*config.enable_fec);
if (config.enable_dtx)
dump_config->set_enable_dtx(*config.enable_dtx);
if (config.num_channels)
dump_config->set_num_channels(*config.num_channels);
DumpEventToFile(event, &dump_file_);
#endif // WEBRTC_ENABLE_PROTOBUF
}
#if WEBRTC_ENABLE_PROTOBUF
void DebugDumpWriterImpl::DumpControllerManagerConfig(
const audio_network_adaptor::config::ControllerManager&
controller_manager_config,
int64_t timestamp) {
Event event;
event.set_timestamp(timestamp);
event.set_type(Event::CONTROLLER_MANAGER_CONFIG);
event.mutable_controller_manager_config()->CopyFrom(
controller_manager_config);
DumpEventToFile(event, &dump_file_);
}
#endif // WEBRTC_ENABLE_PROTOBUF
std::unique_ptr<DebugDumpWriter> DebugDumpWriter::Create(FILE* file_handle) {
return std::unique_ptr<DebugDumpWriter>(new DebugDumpWriterImpl(file_handle));
}
} // namespace webrtc

View File

@ -0,0 +1,55 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_DEBUG_DUMP_WRITER_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_DEBUG_DUMP_WRITER_H_
#include <memory>
#include "modules/audio_coding/audio_network_adaptor/controller.h"
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h"
#include "rtc_base/constructor_magic.h"
#include "rtc_base/ignore_wundef.h"
#include "rtc_base/system/file_wrapper.h"
#if WEBRTC_ENABLE_PROTOBUF
RTC_PUSH_IGNORING_WUNDEF()
#ifdef WEBRTC_ANDROID_PLATFORM_BUILD
#include "external/webrtc/webrtc/modules/audio_coding/audio_network_adaptor/config.pb.h"
#else
#include "modules/audio_coding/audio_network_adaptor/config.pb.h"
#endif
RTC_POP_IGNORING_WUNDEF()
#endif
namespace webrtc {
class DebugDumpWriter {
public:
static std::unique_ptr<DebugDumpWriter> Create(FILE* file_handle);
virtual ~DebugDumpWriter() = default;
virtual void DumpEncoderRuntimeConfig(const AudioEncoderRuntimeConfig& config,
int64_t timestamp) = 0;
virtual void DumpNetworkMetrics(const Controller::NetworkMetrics& metrics,
int64_t timestamp) = 0;
#if WEBRTC_ENABLE_PROTOBUF
virtual void DumpControllerManagerConfig(
const audio_network_adaptor::config::ControllerManager&
controller_manager_config,
int64_t timestamp) = 0;
#endif
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_DEBUG_DUMP_WRITER_H_

View File

@ -0,0 +1,51 @@
/*
* 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/audio_network_adaptor/dtx_controller.h"
#include "rtc_base/checks.h"
namespace webrtc {
DtxController::Config::Config(bool initial_dtx_enabled,
int dtx_enabling_bandwidth_bps,
int dtx_disabling_bandwidth_bps)
: initial_dtx_enabled(initial_dtx_enabled),
dtx_enabling_bandwidth_bps(dtx_enabling_bandwidth_bps),
dtx_disabling_bandwidth_bps(dtx_disabling_bandwidth_bps) {}
DtxController::DtxController(const Config& config)
: config_(config), dtx_enabled_(config_.initial_dtx_enabled) {}
DtxController::~DtxController() = default;
void DtxController::UpdateNetworkMetrics(
const NetworkMetrics& network_metrics) {
if (network_metrics.uplink_bandwidth_bps)
uplink_bandwidth_bps_ = network_metrics.uplink_bandwidth_bps;
}
void DtxController::MakeDecision(AudioEncoderRuntimeConfig* config) {
// Decision on |enable_dtx| should not have been made.
RTC_DCHECK(!config->enable_dtx);
if (uplink_bandwidth_bps_) {
if (dtx_enabled_ &&
*uplink_bandwidth_bps_ >= config_.dtx_disabling_bandwidth_bps) {
dtx_enabled_ = false;
} else if (!dtx_enabled_ &&
*uplink_bandwidth_bps_ <= config_.dtx_enabling_bandwidth_bps) {
dtx_enabled_ = true;
}
}
config->enable_dtx = dtx_enabled_;
}
} // namespace webrtc

View File

@ -0,0 +1,51 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_DTX_CONTROLLER_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_DTX_CONTROLLER_H_
#include "absl/types/optional.h"
#include "modules/audio_coding/audio_network_adaptor/controller.h"
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
class DtxController final : public Controller {
public:
struct Config {
Config(bool initial_dtx_enabled,
int dtx_enabling_bandwidth_bps,
int dtx_disabling_bandwidth_bps);
bool initial_dtx_enabled;
// Uplink bandwidth below which DTX should be switched on.
int dtx_enabling_bandwidth_bps;
// Uplink bandwidth above which DTX should be switched off.
int dtx_disabling_bandwidth_bps;
};
explicit DtxController(const Config& config);
~DtxController() override;
void UpdateNetworkMetrics(const NetworkMetrics& network_metrics) override;
void MakeDecision(AudioEncoderRuntimeConfig* config) override;
private:
const Config config_;
bool dtx_enabled_;
absl::optional<int> uplink_bandwidth_bps_;
RTC_DISALLOW_COPY_AND_ASSIGN(DtxController);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_DTX_CONTROLLER_H_

View File

@ -0,0 +1,82 @@
/*
* 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/audio_network_adaptor/dtx_controller.h"
#include <memory>
#include "test/gtest.h"
namespace webrtc {
namespace {
constexpr int kDtxEnablingBandwidthBps = 55000;
constexpr int kDtxDisablingBandwidthBps = 65000;
constexpr int kMediumBandwidthBps =
(kDtxEnablingBandwidthBps + kDtxDisablingBandwidthBps) / 2;
std::unique_ptr<DtxController> CreateController(int initial_dtx_enabled) {
std::unique_ptr<DtxController> controller(new DtxController(
DtxController::Config(initial_dtx_enabled, kDtxEnablingBandwidthBps,
kDtxDisablingBandwidthBps)));
return controller;
}
void CheckDecision(DtxController* controller,
const absl::optional<int>& uplink_bandwidth_bps,
bool expected_dtx_enabled) {
if (uplink_bandwidth_bps) {
Controller::NetworkMetrics network_metrics;
network_metrics.uplink_bandwidth_bps = uplink_bandwidth_bps;
controller->UpdateNetworkMetrics(network_metrics);
}
AudioEncoderRuntimeConfig config;
controller->MakeDecision(&config);
EXPECT_EQ(expected_dtx_enabled, config.enable_dtx);
}
} // namespace
TEST(DtxControllerTest, OutputInitValueWhenUplinkBandwidthUnknown) {
constexpr bool kInitialDtxEnabled = true;
auto controller = CreateController(kInitialDtxEnabled);
CheckDecision(controller.get(), absl::nullopt, kInitialDtxEnabled);
}
TEST(DtxControllerTest, TurnOnDtxForLowUplinkBandwidth) {
auto controller = CreateController(false);
CheckDecision(controller.get(), kDtxEnablingBandwidthBps, true);
}
TEST(DtxControllerTest, TurnOffDtxForHighUplinkBandwidth) {
auto controller = CreateController(true);
CheckDecision(controller.get(), kDtxDisablingBandwidthBps, false);
}
TEST(DtxControllerTest, MaintainDtxOffForMediumUplinkBandwidth) {
auto controller = CreateController(false);
CheckDecision(controller.get(), kMediumBandwidthBps, false);
}
TEST(DtxControllerTest, MaintainDtxOnForMediumUplinkBandwidth) {
auto controller = CreateController(true);
CheckDecision(controller.get(), kMediumBandwidthBps, true);
}
TEST(DtxControllerTest, CheckBehaviorOnChangingUplinkBandwidth) {
auto controller = CreateController(false);
CheckDecision(controller.get(), kMediumBandwidthBps, false);
CheckDecision(controller.get(), kDtxEnablingBandwidthBps, true);
CheckDecision(controller.get(), kMediumBandwidthBps, true);
CheckDecision(controller.get(), kDtxDisablingBandwidthBps, false);
}
} // namespace webrtc

View File

@ -0,0 +1,78 @@
/*
* 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/audio_network_adaptor/event_log_writer.h"
#include <math.h>
#include <algorithm>
#include <cstdlib>
#include <memory>
#include <utility>
#include "absl/types/optional.h"
#include "api/rtc_event_log/rtc_event.h"
#include "api/rtc_event_log/rtc_event_log.h"
#include "logging/rtc_event_log/events/rtc_event_audio_network_adaptation.h"
#include "rtc_base/checks.h"
namespace webrtc {
EventLogWriter::EventLogWriter(RtcEventLog* event_log,
int min_bitrate_change_bps,
float min_bitrate_change_fraction,
float min_packet_loss_change_fraction)
: event_log_(event_log),
min_bitrate_change_bps_(min_bitrate_change_bps),
min_bitrate_change_fraction_(min_bitrate_change_fraction),
min_packet_loss_change_fraction_(min_packet_loss_change_fraction) {
RTC_DCHECK(event_log_);
}
EventLogWriter::~EventLogWriter() = default;
void EventLogWriter::MaybeLogEncoderConfig(
const AudioEncoderRuntimeConfig& config) {
if (last_logged_config_.num_channels != config.num_channels)
return LogEncoderConfig(config);
if (last_logged_config_.enable_dtx != config.enable_dtx)
return LogEncoderConfig(config);
if (last_logged_config_.enable_fec != config.enable_fec)
return LogEncoderConfig(config);
if (last_logged_config_.frame_length_ms != config.frame_length_ms)
return LogEncoderConfig(config);
if ((!last_logged_config_.bitrate_bps && config.bitrate_bps) ||
(last_logged_config_.bitrate_bps && config.bitrate_bps &&
std::abs(*last_logged_config_.bitrate_bps - *config.bitrate_bps) >=
std::min(static_cast<int>(*last_logged_config_.bitrate_bps *
min_bitrate_change_fraction_),
min_bitrate_change_bps_))) {
return LogEncoderConfig(config);
}
if ((!last_logged_config_.uplink_packet_loss_fraction &&
config.uplink_packet_loss_fraction) ||
(last_logged_config_.uplink_packet_loss_fraction &&
config.uplink_packet_loss_fraction &&
fabs(*last_logged_config_.uplink_packet_loss_fraction -
*config.uplink_packet_loss_fraction) >=
min_packet_loss_change_fraction_ *
*last_logged_config_.uplink_packet_loss_fraction)) {
return LogEncoderConfig(config);
}
}
void EventLogWriter::LogEncoderConfig(const AudioEncoderRuntimeConfig& config) {
auto config_copy = std::make_unique<AudioEncoderRuntimeConfig>(config);
event_log_->Log(
std::make_unique<RtcEventAudioNetworkAdaptation>(std::move(config_copy)));
last_logged_config_ = config;
}
} // namespace webrtc

View File

@ -0,0 +1,42 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_EVENT_LOG_WRITER_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_EVENT_LOG_WRITER_H_
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
class RtcEventLog;
class EventLogWriter final {
public:
EventLogWriter(RtcEventLog* event_log,
int min_bitrate_change_bps,
float min_bitrate_change_fraction,
float min_packet_loss_change_fraction);
~EventLogWriter();
void MaybeLogEncoderConfig(const AudioEncoderRuntimeConfig& config);
private:
void LogEncoderConfig(const AudioEncoderRuntimeConfig& config);
RtcEventLog* const event_log_;
const int min_bitrate_change_bps_;
const float min_bitrate_change_fraction_;
const float min_packet_loss_change_fraction_;
AudioEncoderRuntimeConfig last_logged_config_;
RTC_DISALLOW_COPY_AND_ASSIGN(EventLogWriter);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_EVENT_LOG_WRITER_H_

View File

@ -0,0 +1,240 @@
/*
* 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/audio_network_adaptor/event_log_writer.h"
#include <memory>
#include "logging/rtc_event_log/events/rtc_event_audio_network_adaptation.h"
#include "logging/rtc_event_log/mock/mock_rtc_event_log.h"
#include "rtc_base/checks.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
constexpr int kMinBitrateChangeBps = 5000;
constexpr float kMinPacketLossChangeFraction = 0.5;
constexpr float kMinBitrateChangeFraction = 0.25;
constexpr int kHighBitrateBps = 70000;
constexpr int kLowBitrateBps = 10000;
constexpr int kFrameLengthMs = 60;
constexpr bool kEnableFec = true;
constexpr bool kEnableDtx = true;
constexpr float kPacketLossFraction = 0.05f;
constexpr size_t kNumChannels = 1;
MATCHER_P(IsRtcEventAnaConfigEqualTo, config, "") {
if (arg->GetType() != RtcEvent::Type::AudioNetworkAdaptation) {
return false;
}
auto ana_event = static_cast<RtcEventAudioNetworkAdaptation*>(arg);
return ana_event->config() == config;
}
struct EventLogWriterStates {
std::unique_ptr<EventLogWriter> event_log_writer;
std::unique_ptr<testing::StrictMock<MockRtcEventLog>> event_log;
AudioEncoderRuntimeConfig runtime_config;
};
EventLogWriterStates CreateEventLogWriter() {
EventLogWriterStates state;
state.event_log.reset(new ::testing::StrictMock<MockRtcEventLog>());
state.event_log_writer.reset(new EventLogWriter(
state.event_log.get(), kMinBitrateChangeBps, kMinBitrateChangeFraction,
kMinPacketLossChangeFraction));
state.runtime_config.bitrate_bps = kHighBitrateBps;
state.runtime_config.frame_length_ms = kFrameLengthMs;
state.runtime_config.uplink_packet_loss_fraction = kPacketLossFraction;
state.runtime_config.enable_fec = kEnableFec;
state.runtime_config.enable_dtx = kEnableDtx;
state.runtime_config.num_channels = kNumChannels;
return state;
}
} // namespace
TEST(EventLogWriterTest, FirstConfigIsLogged) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, SameConfigIsNotLogged) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, LogFecStateChange) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
state.runtime_config.enable_fec = !kEnableFec;
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, LogDtxStateChange) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
state.runtime_config.enable_dtx = !kEnableDtx;
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, LogChannelChange) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
state.runtime_config.num_channels = kNumChannels + 1;
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, LogFrameLengthChange) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
state.runtime_config.frame_length_ms = 20;
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, DoNotLogSmallBitrateChange) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
state.runtime_config.bitrate_bps = kHighBitrateBps + kMinBitrateChangeBps - 1;
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, LogLargeBitrateChange) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
// At high bitrate, the min fraction rule requires a larger change than the
// min change rule. We make sure that the min change rule applies.
RTC_DCHECK_GT(kHighBitrateBps * kMinBitrateChangeFraction,
kMinBitrateChangeBps);
state.runtime_config.bitrate_bps = kHighBitrateBps + kMinBitrateChangeBps;
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, LogMinBitrateChangeFractionOnLowBitrateChange) {
auto state = CreateEventLogWriter();
state.runtime_config.bitrate_bps = kLowBitrateBps;
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
// At high bitrate, the min change rule requires a larger change than the min
// fraction rule. We make sure that the min fraction rule applies.
state.runtime_config.bitrate_bps =
kLowBitrateBps + kLowBitrateBps * kMinBitrateChangeFraction;
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, DoNotLogSmallPacketLossFractionChange) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
state.runtime_config.uplink_packet_loss_fraction =
kPacketLossFraction + kMinPacketLossChangeFraction * kPacketLossFraction -
0.001f;
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, LogLargePacketLossFractionChange) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
state.runtime_config.uplink_packet_loss_fraction =
kPacketLossFraction + kMinPacketLossChangeFraction * kPacketLossFraction;
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, LogJustOnceOnMultipleChanges) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
state.runtime_config.uplink_packet_loss_fraction =
kPacketLossFraction + kMinPacketLossChangeFraction * kPacketLossFraction;
state.runtime_config.frame_length_ms = 20;
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
TEST(EventLogWriterTest, LogAfterGradualChange) {
auto state = CreateEventLogWriter();
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
state.runtime_config.bitrate_bps = kHighBitrateBps + kMinBitrateChangeBps;
EXPECT_CALL(*state.event_log,
LogProxy(IsRtcEventAnaConfigEqualTo(state.runtime_config)))
.Times(1);
for (int bitrate_bps = kHighBitrateBps;
bitrate_bps <= kHighBitrateBps + kMinBitrateChangeBps; bitrate_bps++) {
state.runtime_config.bitrate_bps = bitrate_bps;
state.event_log_writer->MaybeLogEncoderConfig(state.runtime_config);
}
}
} // namespace webrtc

View File

@ -0,0 +1,113 @@
/*
* 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/audio_network_adaptor/fec_controller_plr_based.h"
#include <string>
#include <utility>
#include "rtc_base/checks.h"
#include "system_wrappers/include/field_trial.h"
namespace webrtc {
namespace {
class NullSmoothingFilter final : public SmoothingFilter {
public:
void AddSample(float sample) override { last_sample_ = sample; }
absl::optional<float> GetAverage() override { return last_sample_; }
bool SetTimeConstantMs(int time_constant_ms) override {
RTC_NOTREACHED();
return false;
}
private:
absl::optional<float> last_sample_;
};
} // namespace
FecControllerPlrBased::Config::Config(
bool initial_fec_enabled,
const ThresholdCurve& fec_enabling_threshold,
const ThresholdCurve& fec_disabling_threshold,
int time_constant_ms)
: initial_fec_enabled(initial_fec_enabled),
fec_enabling_threshold(fec_enabling_threshold),
fec_disabling_threshold(fec_disabling_threshold),
time_constant_ms(time_constant_ms) {}
FecControllerPlrBased::FecControllerPlrBased(
const Config& config,
std::unique_ptr<SmoothingFilter> smoothing_filter)
: config_(config),
fec_enabled_(config.initial_fec_enabled),
packet_loss_smoother_(std::move(smoothing_filter)) {
RTC_DCHECK(config_.fec_disabling_threshold <= config_.fec_enabling_threshold);
}
FecControllerPlrBased::FecControllerPlrBased(const Config& config)
: FecControllerPlrBased(
config,
webrtc::field_trial::FindFullName("UseTwccPlrForAna") == "Enabled"
? std::unique_ptr<NullSmoothingFilter>(new NullSmoothingFilter())
: std::unique_ptr<SmoothingFilter>(
new SmoothingFilterImpl(config.time_constant_ms))) {}
FecControllerPlrBased::~FecControllerPlrBased() = default;
void FecControllerPlrBased::UpdateNetworkMetrics(
const NetworkMetrics& network_metrics) {
if (network_metrics.uplink_bandwidth_bps)
uplink_bandwidth_bps_ = network_metrics.uplink_bandwidth_bps;
if (network_metrics.uplink_packet_loss_fraction) {
packet_loss_smoother_->AddSample(
*network_metrics.uplink_packet_loss_fraction);
}
}
void FecControllerPlrBased::MakeDecision(AudioEncoderRuntimeConfig* config) {
RTC_DCHECK(!config->enable_fec);
RTC_DCHECK(!config->uplink_packet_loss_fraction);
const auto& packet_loss = packet_loss_smoother_->GetAverage();
fec_enabled_ = fec_enabled_ ? !FecDisablingDecision(packet_loss)
: FecEnablingDecision(packet_loss);
config->enable_fec = fec_enabled_;
config->uplink_packet_loss_fraction = packet_loss ? *packet_loss : 0.0;
}
bool FecControllerPlrBased::FecEnablingDecision(
const absl::optional<float>& packet_loss) const {
if (!uplink_bandwidth_bps_ || !packet_loss) {
return false;
} else {
// Enable when above the curve or exactly on it.
return !config_.fec_enabling_threshold.IsBelowCurve(
{static_cast<float>(*uplink_bandwidth_bps_), *packet_loss});
}
}
bool FecControllerPlrBased::FecDisablingDecision(
const absl::optional<float>& packet_loss) const {
if (!uplink_bandwidth_bps_ || !packet_loss) {
return false;
} else {
// Disable when below the curve.
return config_.fec_disabling_threshold.IsBelowCurve(
{static_cast<float>(*uplink_bandwidth_bps_), *packet_loss});
}
}
} // namespace webrtc

View File

@ -0,0 +1,74 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_FEC_CONTROLLER_PLR_BASED_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_FEC_CONTROLLER_PLR_BASED_H_
#include <memory>
#include "absl/types/optional.h"
#include "common_audio/smoothing_filter.h"
#include "modules/audio_coding/audio_network_adaptor/controller.h"
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h"
#include "modules/audio_coding/audio_network_adaptor/util/threshold_curve.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
class FecControllerPlrBased final : public Controller {
public:
struct Config {
// |fec_enabling_threshold| defines a curve, above which FEC should be
// enabled. |fec_disabling_threshold| defines a curve, under which FEC
// should be disabled. See below
//
// packet-loss ^ | |
// | | | FEC
// | \ \ ON
// | FEC \ \_______ fec_enabling_threshold
// | OFF \_________ fec_disabling_threshold
// |-----------------> bandwidth
Config(bool initial_fec_enabled,
const ThresholdCurve& fec_enabling_threshold,
const ThresholdCurve& fec_disabling_threshold,
int time_constant_ms);
bool initial_fec_enabled;
ThresholdCurve fec_enabling_threshold;
ThresholdCurve fec_disabling_threshold;
int time_constant_ms;
};
// Dependency injection for testing.
FecControllerPlrBased(const Config& config,
std::unique_ptr<SmoothingFilter> smoothing_filter);
explicit FecControllerPlrBased(const Config& config);
~FecControllerPlrBased() override;
void UpdateNetworkMetrics(const NetworkMetrics& network_metrics) override;
void MakeDecision(AudioEncoderRuntimeConfig* config) override;
private:
bool FecEnablingDecision(const absl::optional<float>& packet_loss) const;
bool FecDisablingDecision(const absl::optional<float>& packet_loss) const;
const Config config_;
bool fec_enabled_;
absl::optional<int> uplink_bandwidth_bps_;
const std::unique_ptr<SmoothingFilter> packet_loss_smoother_;
RTC_DISALLOW_COPY_AND_ASSIGN(FecControllerPlrBased);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_FEC_CONTROLLER_PLR_BASED_H_

View File

@ -0,0 +1,489 @@
/*
* 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/audio_network_adaptor/fec_controller_plr_based.h"
#include <utility>
#include "common_audio/mocks/mock_smoothing_filter.h"
#include "test/gtest.h"
namespace webrtc {
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
namespace {
// The test uses the following settings:
//
// packet-loss ^ | |
// | A| C| FEC
// | \ \ ON
// | FEC \ D\_______
// | OFF B\_________
// |-----------------> bandwidth
//
// A : (kDisablingBandwidthLow, kDisablingPacketLossAtLowBw)
// B : (kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw)
// C : (kEnablingBandwidthLow, kEnablingPacketLossAtLowBw)
// D : (kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw)
constexpr int kDisablingBandwidthLow = 15000;
constexpr float kDisablingPacketLossAtLowBw = 0.08f;
constexpr int kDisablingBandwidthHigh = 64000;
constexpr float kDisablingPacketLossAtHighBw = 0.01f;
constexpr int kEnablingBandwidthLow = 17000;
constexpr float kEnablingPacketLossAtLowBw = 0.1f;
constexpr int kEnablingBandwidthHigh = 64000;
constexpr float kEnablingPacketLossAtHighBw = 0.05f;
constexpr float kEpsilon = 1e-5f;
struct FecControllerPlrBasedTestStates {
std::unique_ptr<FecControllerPlrBased> controller;
MockSmoothingFilter* packet_loss_smoother;
};
FecControllerPlrBasedTestStates CreateFecControllerPlrBased(
bool initial_fec_enabled,
const ThresholdCurve& enabling_curve,
const ThresholdCurve& disabling_curve) {
FecControllerPlrBasedTestStates states;
std::unique_ptr<MockSmoothingFilter> mock_smoothing_filter(
new NiceMock<MockSmoothingFilter>());
states.packet_loss_smoother = mock_smoothing_filter.get();
states.controller.reset(new FecControllerPlrBased(
FecControllerPlrBased::Config(initial_fec_enabled, enabling_curve,
disabling_curve, 0),
std::move(mock_smoothing_filter)));
return states;
}
FecControllerPlrBasedTestStates CreateFecControllerPlrBased(
bool initial_fec_enabled) {
return CreateFecControllerPlrBased(
initial_fec_enabled,
ThresholdCurve(kEnablingBandwidthLow, kEnablingPacketLossAtLowBw,
kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw),
ThresholdCurve(kDisablingBandwidthLow, kDisablingPacketLossAtLowBw,
kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw));
}
void UpdateNetworkMetrics(FecControllerPlrBasedTestStates* states,
const absl::optional<int>& uplink_bandwidth_bps,
const absl::optional<float>& uplink_packet_loss) {
// UpdateNetworkMetrics can accept multiple network metric updates at once.
// However, currently, the most used case is to update one metric at a time.
// To reflect this fact, we separate the calls.
if (uplink_bandwidth_bps) {
Controller::NetworkMetrics network_metrics;
network_metrics.uplink_bandwidth_bps = uplink_bandwidth_bps;
states->controller->UpdateNetworkMetrics(network_metrics);
}
if (uplink_packet_loss) {
Controller::NetworkMetrics network_metrics;
network_metrics.uplink_packet_loss_fraction = uplink_packet_loss;
EXPECT_CALL(*states->packet_loss_smoother, AddSample(*uplink_packet_loss));
states->controller->UpdateNetworkMetrics(network_metrics);
// This is called during CheckDecision().
EXPECT_CALL(*states->packet_loss_smoother, GetAverage())
.WillOnce(Return(*uplink_packet_loss));
}
}
// Checks that the FEC decision and |uplink_packet_loss_fraction| given by
// |states->controller->MakeDecision| matches |expected_enable_fec| and
// |expected_uplink_packet_loss_fraction|, respectively.
void CheckDecision(FecControllerPlrBasedTestStates* states,
bool expected_enable_fec,
float expected_uplink_packet_loss_fraction) {
AudioEncoderRuntimeConfig config;
states->controller->MakeDecision(&config);
EXPECT_EQ(expected_enable_fec, config.enable_fec);
EXPECT_EQ(expected_uplink_packet_loss_fraction,
config.uplink_packet_loss_fraction);
}
} // namespace
TEST(FecControllerPlrBasedTest, OutputInitValueBeforeAnyInputsAreReceived) {
for (bool initial_fec_enabled : {false, true}) {
auto states = CreateFecControllerPlrBased(initial_fec_enabled);
CheckDecision(&states, initial_fec_enabled, 0);
}
}
TEST(FecControllerPlrBasedTest, OutputInitValueWhenUplinkBandwidthUnknown) {
// Regardless of the initial FEC state and the packet-loss rate,
// the initial FEC state is maintained as long as the BWE is unknown.
for (bool initial_fec_enabled : {false, true}) {
for (float packet_loss :
{kDisablingPacketLossAtLowBw - kEpsilon, kDisablingPacketLossAtLowBw,
kDisablingPacketLossAtLowBw + kEpsilon,
kEnablingPacketLossAtLowBw - kEpsilon, kEnablingPacketLossAtLowBw,
kEnablingPacketLossAtLowBw + kEpsilon}) {
auto states = CreateFecControllerPlrBased(initial_fec_enabled);
UpdateNetworkMetrics(&states, absl::nullopt, packet_loss);
CheckDecision(&states, initial_fec_enabled, packet_loss);
}
}
}
TEST(FecControllerPlrBasedTest,
OutputInitValueWhenUplinkPacketLossFractionUnknown) {
// Regardless of the initial FEC state and the BWE, the initial FEC state
// is maintained as long as the packet-loss rate is unknown.
for (bool initial_fec_enabled : {false, true}) {
for (int bandwidth : {kDisablingBandwidthLow - 1, kDisablingBandwidthLow,
kDisablingBandwidthLow + 1, kEnablingBandwidthLow - 1,
kEnablingBandwidthLow, kEnablingBandwidthLow + 1}) {
auto states = CreateFecControllerPlrBased(initial_fec_enabled);
UpdateNetworkMetrics(&states, bandwidth, absl::nullopt);
CheckDecision(&states, initial_fec_enabled, 0.0);
}
}
}
TEST(FecControllerPlrBasedTest, EnableFecForHighBandwidth) {
auto states = CreateFecControllerPlrBased(false);
UpdateNetworkMetrics(&states, kEnablingBandwidthHigh,
kEnablingPacketLossAtHighBw);
CheckDecision(&states, true, kEnablingPacketLossAtHighBw);
}
TEST(FecControllerPlrBasedTest, UpdateMultipleNetworkMetricsAtOnce) {
// This test is similar to EnableFecForHighBandwidth. But instead of
// using ::UpdateNetworkMetrics(...), which calls
// FecControllerPlrBased::UpdateNetworkMetrics(...) multiple times, we
// we call it only once. This is to verify that
// FecControllerPlrBased::UpdateNetworkMetrics(...) can handle multiple
// network updates at once. This is, however, not a common use case in current
// audio_network_adaptor_impl.cc.
auto states = CreateFecControllerPlrBased(false);
Controller::NetworkMetrics network_metrics;
network_metrics.uplink_bandwidth_bps = kEnablingBandwidthHigh;
network_metrics.uplink_packet_loss_fraction = kEnablingPacketLossAtHighBw;
EXPECT_CALL(*states.packet_loss_smoother, GetAverage())
.WillOnce(Return(kEnablingPacketLossAtHighBw));
states.controller->UpdateNetworkMetrics(network_metrics);
CheckDecision(&states, true, kEnablingPacketLossAtHighBw);
}
TEST(FecControllerPlrBasedTest, MaintainFecOffForHighBandwidth) {
auto states = CreateFecControllerPlrBased(false);
constexpr float kPacketLoss = kEnablingPacketLossAtHighBw * 0.99f;
UpdateNetworkMetrics(&states, kEnablingBandwidthHigh, kPacketLoss);
CheckDecision(&states, false, kPacketLoss);
}
TEST(FecControllerPlrBasedTest, EnableFecForMediumBandwidth) {
auto states = CreateFecControllerPlrBased(false);
constexpr float kPacketLoss =
(kEnablingPacketLossAtLowBw + kEnablingPacketLossAtHighBw) / 2.0;
UpdateNetworkMetrics(&states,
(kEnablingBandwidthHigh + kEnablingBandwidthLow) / 2,
kPacketLoss);
CheckDecision(&states, true, kPacketLoss);
}
TEST(FecControllerPlrBasedTest, MaintainFecOffForMediumBandwidth) {
auto states = CreateFecControllerPlrBased(false);
constexpr float kPacketLoss =
kEnablingPacketLossAtLowBw * 0.49f + kEnablingPacketLossAtHighBw * 0.51f;
UpdateNetworkMetrics(&states,
(kEnablingBandwidthHigh + kEnablingBandwidthLow) / 2,
kPacketLoss);
CheckDecision(&states, false, kPacketLoss);
}
TEST(FecControllerPlrBasedTest, EnableFecForLowBandwidth) {
auto states = CreateFecControllerPlrBased(false);
UpdateNetworkMetrics(&states, kEnablingBandwidthLow,
kEnablingPacketLossAtLowBw);
CheckDecision(&states, true, kEnablingPacketLossAtLowBw);
}
TEST(FecControllerPlrBasedTest, MaintainFecOffForLowBandwidth) {
auto states = CreateFecControllerPlrBased(false);
constexpr float kPacketLoss = kEnablingPacketLossAtLowBw * 0.99f;
UpdateNetworkMetrics(&states, kEnablingBandwidthLow, kPacketLoss);
CheckDecision(&states, false, kPacketLoss);
}
TEST(FecControllerPlrBasedTest, MaintainFecOffForVeryLowBandwidth) {
auto states = CreateFecControllerPlrBased(false);
// Below |kEnablingBandwidthLow|, no packet loss fraction can cause FEC to
// turn on.
UpdateNetworkMetrics(&states, kEnablingBandwidthLow - 1, 1.0);
CheckDecision(&states, false, 1.0);
}
TEST(FecControllerPlrBasedTest, DisableFecForHighBandwidth) {
auto states = CreateFecControllerPlrBased(true);
constexpr float kPacketLoss = kDisablingPacketLossAtHighBw - kEpsilon;
UpdateNetworkMetrics(&states, kDisablingBandwidthHigh, kPacketLoss);
CheckDecision(&states, false, kPacketLoss);
}
TEST(FecControllerPlrBasedTest, MaintainFecOnForHighBandwidth) {
// Note: Disabling happens when the value is strictly below the threshold.
auto states = CreateFecControllerPlrBased(true);
UpdateNetworkMetrics(&states, kDisablingBandwidthHigh,
kDisablingPacketLossAtHighBw);
CheckDecision(&states, true, kDisablingPacketLossAtHighBw);
}
TEST(FecControllerPlrBasedTest, DisableFecOnMediumBandwidth) {
auto states = CreateFecControllerPlrBased(true);
constexpr float kPacketLoss =
(kDisablingPacketLossAtLowBw + kDisablingPacketLossAtHighBw) / 2.0f -
kEpsilon;
UpdateNetworkMetrics(&states,
(kDisablingBandwidthHigh + kDisablingBandwidthLow) / 2,
kPacketLoss);
CheckDecision(&states, false, kPacketLoss);
}
TEST(FecControllerPlrBasedTest, MaintainFecOnForMediumBandwidth) {
auto states = CreateFecControllerPlrBased(true);
constexpr float kPacketLoss = kDisablingPacketLossAtLowBw * 0.51f +
kDisablingPacketLossAtHighBw * 0.49f - kEpsilon;
UpdateNetworkMetrics(&states,
(kEnablingBandwidthHigh + kDisablingBandwidthLow) / 2,
kPacketLoss);
CheckDecision(&states, true, kPacketLoss);
}
TEST(FecControllerPlrBasedTest, DisableFecForLowBandwidth) {
auto states = CreateFecControllerPlrBased(true);
constexpr float kPacketLoss = kDisablingPacketLossAtLowBw - kEpsilon;
UpdateNetworkMetrics(&states, kDisablingBandwidthLow, kPacketLoss);
CheckDecision(&states, false, kPacketLoss);
}
TEST(FecControllerPlrBasedTest, DisableFecForVeryLowBandwidth) {
auto states = CreateFecControllerPlrBased(true);
// Below |kEnablingBandwidthLow|, any packet loss fraction can cause FEC to
// turn off.
UpdateNetworkMetrics(&states, kDisablingBandwidthLow - 1, 1.0);
CheckDecision(&states, false, 1.0);
}
TEST(FecControllerPlrBasedTest, CheckBehaviorOnChangingNetworkMetrics) {
// In this test, we let the network metrics to traverse from 1 to 5.
// packet-loss ^ 1 | |
// | | 2|
// | \ \ 3
// | \4 \_______
// | \_________
// |---------5-------> bandwidth
auto states = CreateFecControllerPlrBased(true);
UpdateNetworkMetrics(&states, kDisablingBandwidthLow - 1, 1.0);
CheckDecision(&states, false, 1.0);
UpdateNetworkMetrics(&states, kEnablingBandwidthLow,
kEnablingPacketLossAtLowBw * 0.99f);
CheckDecision(&states, false, kEnablingPacketLossAtLowBw * 0.99f);
UpdateNetworkMetrics(&states, kEnablingBandwidthHigh,
kEnablingPacketLossAtHighBw);
CheckDecision(&states, true, kEnablingPacketLossAtHighBw);
UpdateNetworkMetrics(&states, kDisablingBandwidthHigh,
kDisablingPacketLossAtHighBw);
CheckDecision(&states, true, kDisablingPacketLossAtHighBw);
UpdateNetworkMetrics(&states, kDisablingBandwidthHigh + 1, 0.0);
CheckDecision(&states, false, 0.0);
}
TEST(FecControllerPlrBasedTest, CheckBehaviorOnSpecialCurves) {
// We test a special configuration, where the points to define the FEC
// enabling/disabling curves are placed like the following, otherwise the test
// is the same as CheckBehaviorOnChangingNetworkMetrics.
//
// packet-loss ^ | |
// | | C|
// | | |
// | | D|_______
// | A|___B______
// |-----------------> bandwidth
constexpr int kEnablingBandwidthHigh = kEnablingBandwidthLow;
constexpr float kDisablingPacketLossAtLowBw = kDisablingPacketLossAtHighBw;
FecControllerPlrBasedTestStates states;
std::unique_ptr<MockSmoothingFilter> mock_smoothing_filter(
new NiceMock<MockSmoothingFilter>());
states.packet_loss_smoother = mock_smoothing_filter.get();
states.controller.reset(new FecControllerPlrBased(
FecControllerPlrBased::Config(
true,
ThresholdCurve(kEnablingBandwidthLow, kEnablingPacketLossAtLowBw,
kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw),
ThresholdCurve(kDisablingBandwidthLow, kDisablingPacketLossAtLowBw,
kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw),
0),
std::move(mock_smoothing_filter)));
UpdateNetworkMetrics(&states, kDisablingBandwidthLow - 1, 1.0);
CheckDecision(&states, false, 1.0);
UpdateNetworkMetrics(&states, kEnablingBandwidthLow,
kEnablingPacketLossAtHighBw * 0.99f);
CheckDecision(&states, false, kEnablingPacketLossAtHighBw * 0.99f);
UpdateNetworkMetrics(&states, kEnablingBandwidthHigh,
kEnablingPacketLossAtHighBw);
CheckDecision(&states, true, kEnablingPacketLossAtHighBw);
UpdateNetworkMetrics(&states, kDisablingBandwidthHigh,
kDisablingPacketLossAtHighBw);
CheckDecision(&states, true, kDisablingPacketLossAtHighBw);
UpdateNetworkMetrics(&states, kDisablingBandwidthHigh + 1, 0.0);
CheckDecision(&states, false, 0.0);
}
TEST(FecControllerPlrBasedTest, SingleThresholdCurveForEnablingAndDisabling) {
// Note: To avoid numerical errors, keep kPacketLossAtLowBw and
// kPacketLossAthighBw as (negative) integer powers of 2.
// This is mostly relevant for the O3 case.
constexpr int kBandwidthLow = 10000;
constexpr float kPacketLossAtLowBw = 0.25f;
constexpr int kBandwidthHigh = 20000;
constexpr float kPacketLossAtHighBw = 0.125f;
auto curve = ThresholdCurve(kBandwidthLow, kPacketLossAtLowBw, kBandwidthHigh,
kPacketLossAtHighBw);
// B* stands for "below-curve", O* for "on-curve", and A* for "above-curve".
//
// //
// packet-loss ^ //
// | | //
// | B1 O1 //
// | | //
// | O2 //
// | \ A1 //
// | \ //
// | O3 A2 //
// | B2 \ //
// | \ //
// | O4--O5---- //
// | //
// | B3 //
// |-----------------> bandwidth //
struct NetworkState {
int bandwidth;
float packet_loss;
};
std::vector<NetworkState> below{
{kBandwidthLow - 1, kPacketLossAtLowBw + 0.1f}, // B1
{(kBandwidthLow + kBandwidthHigh) / 2,
(kPacketLossAtLowBw + kPacketLossAtHighBw) / 2 - kEpsilon}, // B2
{kBandwidthHigh + 1, kPacketLossAtHighBw - kEpsilon} // B3
};
std::vector<NetworkState> on{
{kBandwidthLow, kPacketLossAtLowBw + 0.1f}, // O1
{kBandwidthLow, kPacketLossAtLowBw}, // O2
{(kBandwidthLow + kBandwidthHigh) / 2,
(kPacketLossAtLowBw + kPacketLossAtHighBw) / 2}, // O3
{kBandwidthHigh, kPacketLossAtHighBw}, // O4
{kBandwidthHigh + 1, kPacketLossAtHighBw}, // O5
};
std::vector<NetworkState> above{
{(kBandwidthLow + kBandwidthHigh) / 2,
(kPacketLossAtLowBw + kPacketLossAtHighBw) / 2 + kEpsilon}, // A1
{kBandwidthHigh + 1, kPacketLossAtHighBw + kEpsilon}, // A2
};
// Test that FEC is turned off whenever we're below the curve, independent
// of the starting FEC state.
for (NetworkState net_state : below) {
for (bool initial_fec_enabled : {false, true}) {
auto states =
CreateFecControllerPlrBased(initial_fec_enabled, curve, curve);
UpdateNetworkMetrics(&states, net_state.bandwidth, net_state.packet_loss);
CheckDecision(&states, false, net_state.packet_loss);
}
}
// Test that FEC is turned on whenever we're on the curve or above it,
// independent of the starting FEC state.
for (const std::vector<NetworkState>& states_list : {on, above}) {
for (NetworkState net_state : states_list) {
for (bool initial_fec_enabled : {false, true}) {
auto states =
CreateFecControllerPlrBased(initial_fec_enabled, curve, curve);
UpdateNetworkMetrics(&states, net_state.bandwidth,
net_state.packet_loss);
CheckDecision(&states, true, net_state.packet_loss);
}
}
}
}
TEST(FecControllerPlrBasedTest, FecAlwaysOff) {
ThresholdCurve always_off_curve(0, 1.0f + kEpsilon, 0, 1.0f + kEpsilon);
for (bool initial_fec_enabled : {false, true}) {
for (int bandwidth : {0, 10000}) {
for (float packet_loss : {0.0f, 0.5f, 1.0f}) {
auto states = CreateFecControllerPlrBased(
initial_fec_enabled, always_off_curve, always_off_curve);
UpdateNetworkMetrics(&states, bandwidth, packet_loss);
CheckDecision(&states, false, packet_loss);
}
}
}
}
TEST(FecControllerPlrBasedTest, FecAlwaysOn) {
ThresholdCurve always_on_curve(0, 0.0f, 0, 0.0f);
for (bool initial_fec_enabled : {false, true}) {
for (int bandwidth : {0, 10000}) {
for (float packet_loss : {0.0f, 0.5f, 1.0f}) {
auto states = CreateFecControllerPlrBased(
initial_fec_enabled, always_on_curve, always_on_curve);
UpdateNetworkMetrics(&states, bandwidth, packet_loss);
CheckDecision(&states, true, packet_loss);
}
}
}
}
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
TEST(FecControllerPlrBasedDeathTest, InvalidConfig) {
FecControllerPlrBasedTestStates states;
std::unique_ptr<MockSmoothingFilter> mock_smoothing_filter(
new NiceMock<MockSmoothingFilter>());
states.packet_loss_smoother = mock_smoothing_filter.get();
EXPECT_DEATH(
states.controller.reset(new FecControllerPlrBased(
FecControllerPlrBased::Config(
true,
ThresholdCurve(kDisablingBandwidthLow - 1,
kEnablingPacketLossAtLowBw, kEnablingBandwidthHigh,
kEnablingPacketLossAtHighBw),
ThresholdCurve(
kDisablingBandwidthLow, kDisablingPacketLossAtLowBw,
kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw),
0),
std::move(mock_smoothing_filter))),
"Check failed");
}
#endif
} // namespace webrtc

View File

@ -0,0 +1,201 @@
/*
* 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/audio_network_adaptor/frame_length_controller.h"
#include <algorithm>
#include <iterator>
#include <utility>
#include "rtc_base/checks.h"
namespace webrtc {
namespace {
constexpr int kPreventOveruseMarginBps = 5000;
int OverheadRateBps(size_t overhead_bytes_per_packet, int frame_length_ms) {
return static_cast<int>(overhead_bytes_per_packet * 8 * 1000 /
frame_length_ms);
}
} // namespace
FrameLengthController::Config::Config(
const std::set<int>& encoder_frame_lengths_ms,
int initial_frame_length_ms,
int min_encoder_bitrate_bps,
float fl_increasing_packet_loss_fraction,
float fl_decreasing_packet_loss_fraction,
int fl_increase_overhead_offset,
int fl_decrease_overhead_offset,
std::map<FrameLengthChange, int> fl_changing_bandwidths_bps)
: encoder_frame_lengths_ms(encoder_frame_lengths_ms),
initial_frame_length_ms(initial_frame_length_ms),
min_encoder_bitrate_bps(min_encoder_bitrate_bps),
fl_increasing_packet_loss_fraction(fl_increasing_packet_loss_fraction),
fl_decreasing_packet_loss_fraction(fl_decreasing_packet_loss_fraction),
fl_increase_overhead_offset(fl_increase_overhead_offset),
fl_decrease_overhead_offset(fl_decrease_overhead_offset),
fl_changing_bandwidths_bps(std::move(fl_changing_bandwidths_bps)) {}
FrameLengthController::Config::Config(const Config& other) = default;
FrameLengthController::Config::~Config() = default;
FrameLengthController::FrameLengthController(const Config& config)
: config_(config) {
frame_length_ms_ = std::find(config_.encoder_frame_lengths_ms.begin(),
config_.encoder_frame_lengths_ms.end(),
config_.initial_frame_length_ms);
// |encoder_frame_lengths_ms| must contain |initial_frame_length_ms|.
RTC_DCHECK(frame_length_ms_ != config_.encoder_frame_lengths_ms.end());
}
FrameLengthController::~FrameLengthController() = default;
void FrameLengthController::UpdateNetworkMetrics(
const NetworkMetrics& network_metrics) {
if (network_metrics.uplink_bandwidth_bps)
uplink_bandwidth_bps_ = network_metrics.uplink_bandwidth_bps;
if (network_metrics.uplink_packet_loss_fraction)
uplink_packet_loss_fraction_ = network_metrics.uplink_packet_loss_fraction;
if (network_metrics.overhead_bytes_per_packet)
overhead_bytes_per_packet_ = network_metrics.overhead_bytes_per_packet;
}
void FrameLengthController::MakeDecision(AudioEncoderRuntimeConfig* config) {
// Decision on |frame_length_ms| should not have been made.
RTC_DCHECK(!config->frame_length_ms);
if (FrameLengthIncreasingDecision(*config)) {
prev_decision_increase_ = true;
} else if (FrameLengthDecreasingDecision(*config)) {
prev_decision_increase_ = false;
}
config->last_fl_change_increase = prev_decision_increase_;
config->frame_length_ms = *frame_length_ms_;
}
FrameLengthController::Config::FrameLengthChange::FrameLengthChange(
int from_frame_length_ms,
int to_frame_length_ms)
: from_frame_length_ms(from_frame_length_ms),
to_frame_length_ms(to_frame_length_ms) {}
bool FrameLengthController::Config::FrameLengthChange::operator<(
const FrameLengthChange& rhs) const {
return from_frame_length_ms < rhs.from_frame_length_ms ||
(from_frame_length_ms == rhs.from_frame_length_ms &&
to_frame_length_ms < rhs.to_frame_length_ms);
}
bool FrameLengthController::FrameLengthIncreasingDecision(
const AudioEncoderRuntimeConfig& config) {
// Increase frame length if
// 1. |uplink_bandwidth_bps| is known to be smaller or equal than
// |min_encoder_bitrate_bps| plus |prevent_overuse_margin_bps| plus the
// current overhead rate OR all the following:
// 2. longer frame length is available AND
// 3. |uplink_bandwidth_bps| is known to be smaller than a threshold AND
// 4. |uplink_packet_loss_fraction| is known to be smaller than a threshold.
// Find next frame length to which a criterion is defined to shift from
// current frame length.
auto longer_frame_length_ms = std::next(frame_length_ms_);
auto increase_threshold = config_.fl_changing_bandwidths_bps.end();
while (longer_frame_length_ms != config_.encoder_frame_lengths_ms.end()) {
increase_threshold = config_.fl_changing_bandwidths_bps.find(
Config::FrameLengthChange(*frame_length_ms_, *longer_frame_length_ms));
if (increase_threshold != config_.fl_changing_bandwidths_bps.end())
break;
longer_frame_length_ms = std::next(longer_frame_length_ms);
}
if (increase_threshold == config_.fl_changing_bandwidths_bps.end())
return false;
// Check that
// -(*overhead_bytes_per_packet_) <= offset <= (*overhead_bytes_per_packet_)
RTC_DCHECK(
!overhead_bytes_per_packet_ ||
(overhead_bytes_per_packet_ &&
static_cast<size_t>(std::max(0, -config_.fl_increase_overhead_offset)) <=
*overhead_bytes_per_packet_ &&
static_cast<size_t>(std::max(0, config_.fl_increase_overhead_offset)) <=
*overhead_bytes_per_packet_));
if (uplink_bandwidth_bps_ && overhead_bytes_per_packet_ &&
*uplink_bandwidth_bps_ <=
config_.min_encoder_bitrate_bps + kPreventOveruseMarginBps +
OverheadRateBps(*overhead_bytes_per_packet_ +
config_.fl_increase_overhead_offset,
*frame_length_ms_)) {
frame_length_ms_ = longer_frame_length_ms;
return true;
}
if ((uplink_bandwidth_bps_ &&
*uplink_bandwidth_bps_ <= increase_threshold->second) &&
(uplink_packet_loss_fraction_ &&
*uplink_packet_loss_fraction_ <=
config_.fl_increasing_packet_loss_fraction)) {
frame_length_ms_ = longer_frame_length_ms;
return true;
}
return false;
}
bool FrameLengthController::FrameLengthDecreasingDecision(
const AudioEncoderRuntimeConfig& config) {
// Decrease frame length if
// 1. shorter frame length is available AND
// 2. |uplink_bandwidth_bps| is known to be bigger than
// |min_encoder_bitrate_bps| plus |prevent_overuse_margin_bps| plus the
// overhead which would be produced with the shorter frame length AND
// one or more of the followings:
// 3. |uplink_bandwidth_bps| is known to be larger than a threshold,
// 4. |uplink_packet_loss_fraction| is known to be larger than a threshold,
// Find next frame length to which a criterion is defined to shift from
// current frame length.
auto shorter_frame_length_ms = frame_length_ms_;
auto decrease_threshold = config_.fl_changing_bandwidths_bps.end();
while (shorter_frame_length_ms != config_.encoder_frame_lengths_ms.begin()) {
shorter_frame_length_ms = std::prev(shorter_frame_length_ms);
decrease_threshold = config_.fl_changing_bandwidths_bps.find(
Config::FrameLengthChange(*frame_length_ms_, *shorter_frame_length_ms));
if (decrease_threshold != config_.fl_changing_bandwidths_bps.end())
break;
}
if (decrease_threshold == config_.fl_changing_bandwidths_bps.end())
return false;
if (uplink_bandwidth_bps_ && overhead_bytes_per_packet_ &&
*uplink_bandwidth_bps_ <=
config_.min_encoder_bitrate_bps + kPreventOveruseMarginBps +
OverheadRateBps(*overhead_bytes_per_packet_ +
config_.fl_decrease_overhead_offset,
*shorter_frame_length_ms)) {
return false;
}
if ((uplink_bandwidth_bps_ &&
*uplink_bandwidth_bps_ >= decrease_threshold->second) ||
(uplink_packet_loss_fraction_ &&
*uplink_packet_loss_fraction_ >=
config_.fl_decreasing_packet_loss_fraction)) {
frame_length_ms_ = shorter_frame_length_ms;
return true;
}
return false;
}
} // namespace webrtc

View File

@ -0,0 +1,93 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_FRAME_LENGTH_CONTROLLER_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_FRAME_LENGTH_CONTROLLER_H_
#include <stddef.h>
#include <map>
#include <set>
#include "absl/types/optional.h"
#include "modules/audio_coding/audio_network_adaptor/controller.h"
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
// Determines target frame length based on the network metrics and the decision
// of FEC controller.
class FrameLengthController final : public Controller {
public:
struct Config {
struct FrameLengthChange {
FrameLengthChange(int from_frame_length_ms, int to_frame_length_ms);
bool operator<(const FrameLengthChange& rhs) const;
int from_frame_length_ms;
int to_frame_length_ms;
};
Config(const std::set<int>& encoder_frame_lengths_ms,
int initial_frame_length_ms,
int min_encoder_bitrate_bps,
float fl_increasing_packet_loss_fraction,
float fl_decreasing_packet_loss_fraction,
int fl_increase_overhead_offset,
int fl_decrease_overhead_offset,
std::map<FrameLengthChange, int> fl_changing_bandwidths_bps);
Config(const Config& other);
~Config();
std::set<int> encoder_frame_lengths_ms;
int initial_frame_length_ms;
int min_encoder_bitrate_bps;
// Uplink packet loss fraction below which frame length can increase.
float fl_increasing_packet_loss_fraction;
// Uplink packet loss fraction below which frame length should decrease.
float fl_decreasing_packet_loss_fraction;
// Offset to apply to overhead calculation when increasing frame length.
int fl_increase_overhead_offset;
// Offset to apply to overhead calculation when decreasing frame length.
int fl_decrease_overhead_offset;
std::map<FrameLengthChange, int> fl_changing_bandwidths_bps;
};
explicit FrameLengthController(const Config& config);
~FrameLengthController() override;
void UpdateNetworkMetrics(const NetworkMetrics& network_metrics) override;
void MakeDecision(AudioEncoderRuntimeConfig* config) override;
private:
bool FrameLengthIncreasingDecision(const AudioEncoderRuntimeConfig& config);
bool FrameLengthDecreasingDecision(const AudioEncoderRuntimeConfig& config);
const Config config_;
std::set<int>::const_iterator frame_length_ms_;
absl::optional<int> uplink_bandwidth_bps_;
absl::optional<float> uplink_packet_loss_fraction_;
absl::optional<size_t> overhead_bytes_per_packet_;
// True if the previous frame length decision was an increase, otherwise
// false.
bool prev_decision_increase_ = false;
RTC_DISALLOW_COPY_AND_ASSIGN(FrameLengthController);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_FRAME_LENGTH_CONTROLLER_H_

View File

@ -0,0 +1,444 @@
/*
* 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/audio_network_adaptor/frame_length_controller.h"
#include <memory>
#include <utility>
#include "test/gtest.h"
namespace webrtc {
namespace {
constexpr float kFlIncreasingPacketLossFraction = 0.04f;
constexpr float kFlDecreasingPacketLossFraction = 0.05f;
constexpr int kFlIncreaseOverheadOffset = 0;
constexpr int kFlDecreaseOverheadOffset = 0;
constexpr int kMinEncoderBitrateBps = 6000;
constexpr int kPreventOveruseMarginBps = 5000;
constexpr size_t kOverheadBytesPerPacket = 20;
constexpr int kFl20msTo60msBandwidthBps = 40000;
constexpr int kFl60msTo20msBandwidthBps = 50000;
constexpr int kFl60msTo120msBandwidthBps = 30000;
constexpr int kFl120msTo60msBandwidthBps = 40000;
constexpr int kFl20msTo40msBandwidthBps = 45000;
constexpr int kFl40msTo20msBandwidthBps = 50000;
constexpr int kFl40msTo60msBandwidthBps = 40000;
constexpr int kFl60msTo40msBandwidthBps = 45000;
constexpr int kMediumBandwidthBps =
(kFl40msTo20msBandwidthBps + kFl20msTo40msBandwidthBps) / 2;
constexpr float kMediumPacketLossFraction =
(kFlDecreasingPacketLossFraction + kFlIncreasingPacketLossFraction) / 2;
const std::set<int> kDefaultEncoderFrameLengthsMs = {20, 40, 60, 120};
int VeryLowBitrate(int frame_length_ms) {
return kMinEncoderBitrateBps + kPreventOveruseMarginBps +
(kOverheadBytesPerPacket * 8 * 1000 / frame_length_ms);
}
std::unique_ptr<FrameLengthController> CreateController(
const std::map<FrameLengthController::Config::FrameLengthChange, int>&
frame_length_change_criteria,
const std::set<int>& encoder_frame_lengths_ms,
int initial_frame_length_ms) {
std::unique_ptr<FrameLengthController> controller(
new FrameLengthController(FrameLengthController::Config(
encoder_frame_lengths_ms, initial_frame_length_ms,
kMinEncoderBitrateBps, kFlIncreasingPacketLossFraction,
kFlDecreasingPacketLossFraction, kFlIncreaseOverheadOffset,
kFlDecreaseOverheadOffset, frame_length_change_criteria)));
return controller;
}
std::map<FrameLengthController::Config::FrameLengthChange, int>
CreateChangeCriteriaFor20msAnd60ms() {
return std::map<FrameLengthController::Config::FrameLengthChange, int>{
{FrameLengthController::Config::FrameLengthChange(20, 60),
kFl20msTo60msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(60, 20),
kFl60msTo20msBandwidthBps}};
}
std::map<FrameLengthController::Config::FrameLengthChange, int>
CreateChangeCriteriaFor20msAnd40ms() {
return std::map<FrameLengthController::Config::FrameLengthChange, int>{
{FrameLengthController::Config::FrameLengthChange(20, 40),
kFl20msTo40msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(40, 20),
kFl40msTo20msBandwidthBps}};
}
std::map<FrameLengthController::Config::FrameLengthChange, int>
CreateChangeCriteriaFor20ms60msAnd120ms() {
return std::map<FrameLengthController::Config::FrameLengthChange, int>{
{FrameLengthController::Config::FrameLengthChange(20, 60),
kFl20msTo60msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(60, 20),
kFl60msTo20msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(60, 120),
kFl60msTo120msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(120, 60),
kFl120msTo60msBandwidthBps}};
}
std::map<FrameLengthController::Config::FrameLengthChange, int>
CreateChangeCriteriaFor20ms40ms60msAnd120ms() {
return std::map<FrameLengthController::Config::FrameLengthChange, int>{
{FrameLengthController::Config::FrameLengthChange(20, 60),
kFl20msTo60msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(60, 20),
kFl60msTo20msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(20, 40),
kFl20msTo40msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(40, 20),
kFl40msTo20msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(40, 60),
kFl40msTo60msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(60, 40),
kFl60msTo40msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(60, 120),
kFl60msTo120msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(120, 60),
kFl120msTo60msBandwidthBps}};
}
std::map<FrameLengthController::Config::FrameLengthChange, int>
CreateChangeCriteriaFor40msAnd60ms() {
return std::map<FrameLengthController::Config::FrameLengthChange, int>{
{FrameLengthController::Config::FrameLengthChange(40, 60),
kFl40msTo60msBandwidthBps},
{FrameLengthController::Config::FrameLengthChange(60, 40),
kFl60msTo40msBandwidthBps}};
}
void UpdateNetworkMetrics(
FrameLengthController* controller,
const absl::optional<int>& uplink_bandwidth_bps,
const absl::optional<float>& uplink_packet_loss_fraction,
const absl::optional<size_t>& overhead_bytes_per_packet) {
// UpdateNetworkMetrics can accept multiple network metric updates at once.
// However, currently, the most used case is to update one metric at a time.
// To reflect this fact, we separate the calls.
if (uplink_bandwidth_bps) {
Controller::NetworkMetrics network_metrics;
network_metrics.uplink_bandwidth_bps = uplink_bandwidth_bps;
controller->UpdateNetworkMetrics(network_metrics);
}
if (uplink_packet_loss_fraction) {
Controller::NetworkMetrics network_metrics;
network_metrics.uplink_packet_loss_fraction = uplink_packet_loss_fraction;
controller->UpdateNetworkMetrics(network_metrics);
}
if (overhead_bytes_per_packet) {
Controller::NetworkMetrics network_metrics;
network_metrics.overhead_bytes_per_packet = overhead_bytes_per_packet;
controller->UpdateNetworkMetrics(network_metrics);
}
}
void CheckDecision(FrameLengthController* controller,
int expected_frame_length_ms) {
AudioEncoderRuntimeConfig config;
controller->MakeDecision(&config);
EXPECT_EQ(expected_frame_length_ms, config.frame_length_ms);
}
} // namespace
TEST(FrameLengthControllerTest, DecreaseTo20MsOnHighUplinkBandwidth) {
auto controller = CreateController(CreateChangeCriteriaFor20msAnd60ms(),
kDefaultEncoderFrameLengthsMs, 60);
UpdateNetworkMetrics(controller.get(), kFl60msTo20msBandwidthBps,
absl::nullopt, kOverheadBytesPerPacket);
CheckDecision(controller.get(), 20);
}
TEST(FrameLengthControllerTest, DecreaseTo20MsOnHighUplinkPacketLossFraction) {
auto controller = CreateController(CreateChangeCriteriaFor20msAnd60ms(),
kDefaultEncoderFrameLengthsMs, 60);
UpdateNetworkMetrics(controller.get(), absl::nullopt,
kFlDecreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 20);
}
TEST(FrameLengthControllerTest,
Maintain60MsIf20MsNotInReceiverFrameLengthRange) {
auto controller =
CreateController(CreateChangeCriteriaFor20msAnd60ms(), {60}, 60);
// Set FEC on that would cause frame length to decrease if receiver frame
// length range included 20ms.
CheckDecision(controller.get(), 60);
}
TEST(FrameLengthControllerTest, IncreaseTo40MsOnMultipleConditions) {
// Increase to 40ms frame length if
// 1. |uplink_bandwidth_bps| is known to be smaller than a threshold AND
// 2. |uplink_packet_loss_fraction| is known to be smaller than a threshold
// AND
// 3. FEC is not decided or OFF.
auto controller = CreateController(CreateChangeCriteriaFor20msAnd40ms(),
kDefaultEncoderFrameLengthsMs, 20);
UpdateNetworkMetrics(controller.get(), kFl20msTo40msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 40);
}
TEST(FrameLengthControllerTest, DecreaseTo40MsOnHighUplinkBandwidth) {
auto controller = CreateController(CreateChangeCriteriaFor40msAnd60ms(),
kDefaultEncoderFrameLengthsMs, 40);
UpdateNetworkMetrics(controller.get(), kFl60msTo40msBandwidthBps,
absl::nullopt, kOverheadBytesPerPacket);
CheckDecision(controller.get(), 40);
}
TEST(FrameLengthControllerTest, Maintain60MsOnMultipleConditions) {
// Maintain 60ms frame length if
// 1. |uplink_bandwidth_bps| is at medium level,
// 2. |uplink_packet_loss_fraction| is at medium,
// 3. FEC is not decided ON.
auto controller = CreateController(CreateChangeCriteriaFor20msAnd60ms(),
kDefaultEncoderFrameLengthsMs, 60);
UpdateNetworkMetrics(controller.get(), kMediumBandwidthBps,
kMediumPacketLossFraction, kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
}
TEST(FrameLengthControllerTest, IncreaseTo60MsOnMultipleConditions) {
// Increase to 60ms frame length if
// 1. |uplink_bandwidth_bps| is known to be smaller than a threshold AND
// 2. |uplink_packet_loss_fraction| is known to be smaller than a threshold
// AND
// 3. FEC is not decided or OFF.
auto controller = CreateController(CreateChangeCriteriaFor20msAnd60ms(),
kDefaultEncoderFrameLengthsMs, 20);
UpdateNetworkMetrics(controller.get(), kFl20msTo60msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
}
TEST(FrameLengthControllerTest, IncreaseTo60MsOnVeryLowUplinkBandwidth) {
auto controller = CreateController(CreateChangeCriteriaFor20msAnd60ms(),
kDefaultEncoderFrameLengthsMs, 20);
// We set packet loss fraction to kFlDecreasingPacketLossFraction, which
// should have prevented frame length to increase, if the uplink bandwidth
// was not this low.
UpdateNetworkMetrics(controller.get(), VeryLowBitrate(20),
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
}
TEST(FrameLengthControllerTest, Maintain60MsOnVeryLowUplinkBandwidth) {
auto controller = CreateController(CreateChangeCriteriaFor20msAnd60ms(),
kDefaultEncoderFrameLengthsMs, 60);
// We set packet loss fraction to FlDecreasingPacketLossFraction, which should
// have caused the frame length to decrease, if the uplink bandwidth was not
// this low.
UpdateNetworkMetrics(controller.get(), VeryLowBitrate(20),
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
}
TEST(FrameLengthControllerTest, UpdateMultipleNetworkMetricsAtOnce) {
// This test is similar to IncreaseTo60MsOnMultipleConditions. But instead of
// using ::UpdateNetworkMetrics(...), which calls
// FrameLengthController::UpdateNetworkMetrics(...) multiple times, we
// we call it only once. This is to verify that
// FrameLengthController::UpdateNetworkMetrics(...) can handle multiple
// network updates at once. This is, however, not a common use case in current
// audio_network_adaptor_impl.cc.
auto controller = CreateController(CreateChangeCriteriaFor20msAnd60ms(),
kDefaultEncoderFrameLengthsMs, 20);
Controller::NetworkMetrics network_metrics;
network_metrics.uplink_bandwidth_bps = kFl20msTo60msBandwidthBps;
network_metrics.uplink_packet_loss_fraction = kFlIncreasingPacketLossFraction;
controller->UpdateNetworkMetrics(network_metrics);
CheckDecision(controller.get(), 60);
}
TEST(FrameLengthControllerTest,
Maintain20MsIf60MsNotInReceiverFrameLengthRange) {
auto controller =
CreateController(CreateChangeCriteriaFor20msAnd60ms(), {20}, 20);
// Use a low uplink bandwidth and a low uplink packet loss fraction that would
// cause frame length to increase if receiver frame length included 60ms.
UpdateNetworkMetrics(controller.get(), kFl20msTo60msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 20);
}
TEST(FrameLengthControllerTest, Maintain20MsOnMediumUplinkBandwidth) {
auto controller = CreateController(CreateChangeCriteriaFor20msAnd60ms(),
kDefaultEncoderFrameLengthsMs, 20);
UpdateNetworkMetrics(controller.get(), kMediumBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 20);
}
TEST(FrameLengthControllerTest, Maintain20MsOnMediumUplinkPacketLossFraction) {
auto controller = CreateController(CreateChangeCriteriaFor20msAnd60ms(),
kDefaultEncoderFrameLengthsMs, 20);
// Use a low uplink bandwidth that would cause frame length to increase if
// uplink packet loss fraction was low.
UpdateNetworkMetrics(controller.get(), kFl20msTo60msBandwidthBps,
kMediumPacketLossFraction, kOverheadBytesPerPacket);
CheckDecision(controller.get(), 20);
}
TEST(FrameLengthControllerTest, Maintain60MsWhenNo120msCriteriaIsSet) {
auto controller = CreateController(CreateChangeCriteriaFor20msAnd60ms(),
kDefaultEncoderFrameLengthsMs, 60);
UpdateNetworkMetrics(controller.get(), kFl60msTo120msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
}
TEST(FrameLengthControllerTest, From120MsTo20MsOnHighUplinkBandwidth) {
auto controller = CreateController(CreateChangeCriteriaFor20ms60msAnd120ms(),
kDefaultEncoderFrameLengthsMs, 120);
// It takes two steps for frame length to go from 120ms to 20ms.
UpdateNetworkMetrics(controller.get(), kFl60msTo20msBandwidthBps,
absl::nullopt, kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
UpdateNetworkMetrics(controller.get(), kFl60msTo20msBandwidthBps,
absl::nullopt, kOverheadBytesPerPacket);
CheckDecision(controller.get(), 20);
}
TEST(FrameLengthControllerTest, From120MsTo20MsOnHighUplinkPacketLossFraction) {
auto controller = CreateController(CreateChangeCriteriaFor20ms60msAnd120ms(),
kDefaultEncoderFrameLengthsMs, 120);
// It takes two steps for frame length to go from 120ms to 20ms.
UpdateNetworkMetrics(controller.get(), absl::nullopt,
kFlDecreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
UpdateNetworkMetrics(controller.get(), absl::nullopt,
kFlDecreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 20);
}
TEST(FrameLengthControllerTest, Maintain120MsOnVeryLowUplinkBandwidth) {
auto controller = CreateController(CreateChangeCriteriaFor20ms60msAnd120ms(),
kDefaultEncoderFrameLengthsMs, 120);
// We set packet loss fraction to FlDecreasingPacketLossFraction, which should
// have caused the frame length to decrease, if the uplink bandwidth was not
// this low.
UpdateNetworkMetrics(controller.get(), VeryLowBitrate(60),
kFlDecreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 120);
}
TEST(FrameLengthControllerTest, From60MsTo120MsOnVeryLowUplinkBandwidth) {
auto controller = CreateController(CreateChangeCriteriaFor20ms60msAnd120ms(),
kDefaultEncoderFrameLengthsMs, 60);
// We set packet loss fraction to FlDecreasingPacketLossFraction, which should
// have prevented frame length to increase, if the uplink bandwidth was not
// this low.
UpdateNetworkMetrics(controller.get(), VeryLowBitrate(60),
kFlDecreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 120);
}
TEST(FrameLengthControllerTest, From20MsTo120MsOnMultipleConditions) {
// Increase to 120ms frame length if
// 1. |uplink_bandwidth_bps| is known to be smaller than a threshold AND
// 2. |uplink_packet_loss_fraction| is known to be smaller than a threshold.
auto controller = CreateController(CreateChangeCriteriaFor20ms60msAnd120ms(),
kDefaultEncoderFrameLengthsMs, 20);
// It takes two steps for frame length to go from 20ms to 120ms.
UpdateNetworkMetrics(controller.get(), kFl60msTo120msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
UpdateNetworkMetrics(controller.get(), kFl60msTo120msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 120);
}
TEST(FrameLengthControllerTest, Stall60MsIf120MsNotInReceiverFrameLengthRange) {
auto controller =
CreateController(CreateChangeCriteriaFor20ms60msAnd120ms(), {20, 60}, 20);
UpdateNetworkMetrics(controller.get(), kFl60msTo120msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
UpdateNetworkMetrics(controller.get(), kFl60msTo120msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
}
TEST(FrameLengthControllerTest, CheckBehaviorOnChangingNetworkMetrics) {
auto controller =
CreateController(CreateChangeCriteriaFor20ms40ms60msAnd120ms(),
kDefaultEncoderFrameLengthsMs, 20);
UpdateNetworkMetrics(controller.get(), kMediumBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 20);
UpdateNetworkMetrics(controller.get(), kFl20msTo40msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 40);
UpdateNetworkMetrics(controller.get(), kFl60msTo40msBandwidthBps,
kMediumPacketLossFraction, kOverheadBytesPerPacket);
CheckDecision(controller.get(), 40);
UpdateNetworkMetrics(controller.get(), kFl20msTo60msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
UpdateNetworkMetrics(controller.get(), kFl60msTo120msBandwidthBps,
kMediumPacketLossFraction, kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
UpdateNetworkMetrics(controller.get(), kFl60msTo120msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 120);
UpdateNetworkMetrics(controller.get(), kFl120msTo60msBandwidthBps,
kFlIncreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 60);
UpdateNetworkMetrics(controller.get(), kFl60msTo40msBandwidthBps,
kFlDecreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 40);
UpdateNetworkMetrics(controller.get(), kMediumBandwidthBps,
kFlDecreasingPacketLossFraction,
kOverheadBytesPerPacket);
CheckDecision(controller.get(), 20);
}
} // namespace webrtc

View File

@ -0,0 +1,49 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_INCLUDE_AUDIO_NETWORK_ADAPTOR_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_INCLUDE_AUDIO_NETWORK_ADAPTOR_H_
#include "absl/types/optional.h"
#include "api/audio_codecs/audio_encoder.h"
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h"
namespace webrtc {
// An AudioNetworkAdaptor optimizes the audio experience by suggesting a
// suitable runtime configuration (bit rate, frame length, FEC, etc.) to the
// encoder based on network metrics.
class AudioNetworkAdaptor {
public:
virtual ~AudioNetworkAdaptor() = default;
virtual void SetUplinkBandwidth(int uplink_bandwidth_bps) = 0;
virtual void SetUplinkPacketLossFraction(
float uplink_packet_loss_fraction) = 0;
virtual void SetRtt(int rtt_ms) = 0;
virtual void SetTargetAudioBitrate(int target_audio_bitrate_bps) = 0;
virtual void SetOverhead(size_t overhead_bytes_per_packet) = 0;
virtual AudioEncoderRuntimeConfig GetEncoderRuntimeConfig() = 0;
virtual void StartDebugDump(FILE* file_handle) = 0;
virtual void StopDebugDump() = 0;
virtual ANAStats GetStats() const = 0;
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_INCLUDE_AUDIO_NETWORK_ADAPTOR_H_

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2017 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_INCLUDE_AUDIO_NETWORK_ADAPTOR_CONFIG_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_INCLUDE_AUDIO_NETWORK_ADAPTOR_CONFIG_H_
#include <stddef.h>
#include "absl/types/optional.h"
namespace webrtc {
struct AudioEncoderRuntimeConfig {
AudioEncoderRuntimeConfig();
AudioEncoderRuntimeConfig(const AudioEncoderRuntimeConfig& other);
~AudioEncoderRuntimeConfig();
AudioEncoderRuntimeConfig& operator=(const AudioEncoderRuntimeConfig& other);
bool operator==(const AudioEncoderRuntimeConfig& other) const;
absl::optional<int> bitrate_bps;
absl::optional<int> frame_length_ms;
// Note: This is what we tell the encoder. It doesn't have to reflect
// the actual NetworkMetrics; it's subject to our decision.
absl::optional<float> uplink_packet_loss_fraction;
absl::optional<bool> enable_fec;
absl::optional<bool> enable_dtx;
// Some encoders can encode fewer channels than the actual input to make
// better use of the bandwidth. |num_channels| sets the number of channels
// to encode.
absl::optional<size_t> num_channels;
// This is true if the last frame length change was an increase, and otherwise
// false.
// The value of this boolean is used to apply a different offset to the
// per-packet overhead that is reported by the BWE. The exact offset value
// is most important right after a frame length change, because the frame
// length change affects the overhead. In the steady state, the exact value is
// not important because the BWE will compensate.
bool last_fl_change_increase = false;
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_INCLUDE_AUDIO_NETWORK_ADAPTOR_CONFIG_H_

View File

@ -0,0 +1,46 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_AUDIO_NETWORK_ADAPTOR_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_AUDIO_NETWORK_ADAPTOR_H_
#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h"
#include "test/gmock.h"
namespace webrtc {
class MockAudioNetworkAdaptor : public AudioNetworkAdaptor {
public:
virtual ~MockAudioNetworkAdaptor() { Die(); }
MOCK_METHOD0(Die, void());
MOCK_METHOD1(SetUplinkBandwidth, void(int uplink_bandwidth_bps));
MOCK_METHOD1(SetUplinkPacketLossFraction,
void(float uplink_packet_loss_fraction));
MOCK_METHOD1(SetRtt, void(int rtt_ms));
MOCK_METHOD1(SetTargetAudioBitrate, void(int target_audio_bitrate_bps));
MOCK_METHOD1(SetOverhead, void(size_t overhead_bytes_per_packet));
MOCK_METHOD0(GetEncoderRuntimeConfig, AudioEncoderRuntimeConfig());
MOCK_METHOD1(StartDebugDump, void(FILE* file_handle));
MOCK_METHOD0(StopDebugDump, void());
MOCK_CONST_METHOD0(GetStats, ANAStats());
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_AUDIO_NETWORK_ADAPTOR_H_

View File

@ -0,0 +1,30 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_CONTROLLER_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_CONTROLLER_H_
#include "modules/audio_coding/audio_network_adaptor/controller.h"
#include "test/gmock.h"
namespace webrtc {
class MockController : public Controller {
public:
virtual ~MockController() { Die(); }
MOCK_METHOD0(Die, void());
MOCK_METHOD1(UpdateNetworkMetrics,
void(const NetworkMetrics& network_metrics));
MOCK_METHOD1(MakeDecision, void(AudioEncoderRuntimeConfig* config));
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_CONTROLLER_H_

View File

@ -0,0 +1,33 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_CONTROLLER_MANAGER_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_CONTROLLER_MANAGER_H_
#include <vector>
#include "modules/audio_coding/audio_network_adaptor/controller_manager.h"
#include "test/gmock.h"
namespace webrtc {
class MockControllerManager : public ControllerManager {
public:
virtual ~MockControllerManager() { Die(); }
MOCK_METHOD0(Die, void());
MOCK_METHOD1(
GetSortedControllers,
std::vector<Controller*>(const Controller::NetworkMetrics& metrics));
MOCK_CONST_METHOD0(GetControllers, std::vector<Controller*>());
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_CONTROLLER_MANAGER_H_

View File

@ -0,0 +1,40 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_DEBUG_DUMP_WRITER_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_DEBUG_DUMP_WRITER_H_
#include "modules/audio_coding/audio_network_adaptor/debug_dump_writer.h"
#include "test/gmock.h"
namespace webrtc {
class MockDebugDumpWriter : public DebugDumpWriter {
public:
virtual ~MockDebugDumpWriter() { Die(); }
MOCK_METHOD0(Die, void());
MOCK_METHOD2(DumpEncoderRuntimeConfig,
void(const AudioEncoderRuntimeConfig& config,
int64_t timestamp));
MOCK_METHOD2(DumpNetworkMetrics,
void(const Controller::NetworkMetrics& metrics,
int64_t timestamp));
#if WEBRTC_ENABLE_PROTOBUF
MOCK_METHOD2(DumpControllerManagerConfig,
void(const audio_network_adaptor::config::ControllerManager&
controller_manager_config,
int64_t timestamp));
#endif
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_DEBUG_DUMP_WRITER_H_

View File

@ -0,0 +1,147 @@
#!/usr/bin/python2
# Copyright (c) 2017 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.
# To run this script please copy "out/<build_name>//pyproto/webrtc/modules/
# audio_coding/audio_network_adaptor/debug_dump_pb2.py" to this folder.
# The you can run this script with:
# "python parse_ana_dump.py -m uplink_bandwidth_bps -f dump_file.dat"
# You can add as may metrics or decisions to the plot as you like.
# form more information call:
# "python parse_ana_dump.py --help"
import struct
from optparse import OptionParser
import matplotlib.pyplot as plt
import debug_dump_pb2
def GetNextMessageSize(file_to_parse):
data = file_to_parse.read(4)
if data == '':
return 0
return struct.unpack('<I', data)[0]
def GetNextMessageFromFile(file_to_parse):
message_size = GetNextMessageSize(file_to_parse)
if message_size == 0:
return None
try:
event = debug_dump_pb2.Event()
event.ParseFromString(file_to_parse.read(message_size))
except IOError:
print 'Invalid message in file'
return None
return event
def InitMetrics():
metrics = {}
event = debug_dump_pb2.Event()
for metric in event.network_metrics.DESCRIPTOR.fields:
metrics[metric.name] = {'time': [], 'value': []}
return metrics
def InitDecisions():
decisions = {}
event = debug_dump_pb2.Event()
for decision in event.encoder_runtime_config.DESCRIPTOR.fields:
decisions[decision.name] = {'time': [], 'value': []}
return decisions
def ParseAnaDump(dump_file_to_parse):
with open(dump_file_to_parse, 'rb') as file_to_parse:
metrics = InitMetrics()
decisions = InitDecisions()
first_time_stamp = None
while True:
event = GetNextMessageFromFile(file_to_parse)
if event is None:
break
if first_time_stamp is None:
first_time_stamp = event.timestamp
if event.type == debug_dump_pb2.Event.ENCODER_RUNTIME_CONFIG:
for decision in event.encoder_runtime_config.DESCRIPTOR.fields:
if event.encoder_runtime_config.HasField(decision.name):
decisions[decision.name]['time'].append(event.timestamp -
first_time_stamp)
decisions[decision.name]['value'].append(
getattr(event.encoder_runtime_config, decision.name))
if event.type == debug_dump_pb2.Event.NETWORK_METRICS:
for metric in event.network_metrics.DESCRIPTOR.fields:
if event.network_metrics.HasField(metric.name):
metrics[metric.name]['time'].append(event.timestamp -
first_time_stamp)
metrics[metric.name]['value'].append(
getattr(event.network_metrics, metric.name))
return (metrics, decisions)
def main():
parser = OptionParser()
parser.add_option(
"-f", "--dump_file", dest="dump_file_to_parse", help="dump file to parse")
parser.add_option(
'-m',
'--metric_plot',
default=[],
type=str,
help='metric key (name of the metric) to plot',
dest='metric_keys',
action='append')
parser.add_option(
'-d',
'--decision_plot',
default=[],
type=str,
help='decision key (name of the decision) to plot',
dest='decision_keys',
action='append')
options = parser.parse_args()[0]
if options.dump_file_to_parse is None:
print "No dump file to parse is set.\n"
parser.print_help()
exit()
(metrics, decisions) = ParseAnaDump(options.dump_file_to_parse)
metric_keys = options.metric_keys
decision_keys = options.decision_keys
plot_count = len(metric_keys) + len(decision_keys)
if plot_count == 0:
print "You have to set at least one metric or decision to plot.\n"
parser.print_help()
exit()
plots = []
if plot_count == 1:
f, mp_plot = plt.subplots()
plots.append(mp_plot)
else:
f, mp_plots = plt.subplots(plot_count, sharex=True)
plots.extend(mp_plots.tolist())
for key in metric_keys:
plot = plots.pop()
plot.grid(True)
plot.set_title(key + " (metric)")
plot.plot(metrics[key]['time'], metrics[key]['value'])
for key in decision_keys:
plot = plots.pop()
plot.grid(True)
plot.set_title(key + " (decision)")
plot.plot(decisions[key]['time'], decisions[key]['value'])
f.subplots_adjust(hspace=0.3)
plt.show()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,118 @@
/*
* Copyright (c) 2017 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.
*/
#ifndef MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_UTIL_THRESHOLD_CURVE_H_
#define MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_UTIL_THRESHOLD_CURVE_H_
#include "rtc_base/checks.h"
namespace webrtc {
class ThresholdCurve {
public:
struct Point {
constexpr Point(float x, float y) : x(x), y(y) {}
float x;
float y;
};
// ThresholdCurve defines a curve. The curve is characterized by the two
// conjunction points: A and B. The curve segments the metric space into
// three domains - above the curve, on it and below it.
//
// y-axis ^ |
// | A|
// | \ A: (a.x, a.y)
// | \ B: (b.x, b.y)
// | B\________
// |---------------> bandwidth
//
// If either a.x == b.x or a.y == b.y, the curve can be defined
// by a single point. (We merge the two points into one - either the lower or
// the leftmost one - for easier treatment.)
//
// y-axis ^ |
// | |
// | |
// | |
// | P|__________
// |---------------> bandwidth
ThresholdCurve(const Point& left, const Point& right)
: a(GetPoint(left, right, true)),
b(GetPoint(left, right, false)),
slope(b.x - a.x == 0.0f ? 0.0f : (b.y - a.y) / (b.x - a.x)),
offset(a.y - slope * a.x) {
// TODO(eladalon): We might want to introduce some numerical validations.
}
ThresholdCurve(float a_x, float a_y, float b_x, float b_y)
: ThresholdCurve(Point{a_x, a_y}, Point{b_x, b_y}) {}
// Checks if a point is strictly below the curve.
bool IsBelowCurve(const Point& p) const {
if (p.x < a.x) {
return true;
} else if (p.x == a.x) {
// In principle, we could merge this into the next else, but to avoid
// numerical errors, we treat it separately.
return p.y < a.y;
} else if (a.x < p.x && p.x < b.x) {
return p.y < offset + slope * p.x;
} else { // if (b.x <= p.x)
return p.y < b.y;
}
}
// Checks if a point is strictly above the curve.
bool IsAboveCurve(const Point& p) const {
if (p.x <= a.x) {
return false;
} else if (a.x < p.x && p.x < b.x) {
return p.y > offset + slope * p.x;
} else { // if (b.x <= p.x)
return p.y > b.y;
}
}
bool operator<=(const ThresholdCurve& rhs) const {
// This curve is <= the rhs curve if no point from this curve is
// above a corresponding point from the rhs curve.
return !IsBelowCurve(rhs.a) && !IsBelowCurve(rhs.b) &&
!rhs.IsAboveCurve(a) && !rhs.IsAboveCurve(b);
}
private:
static const Point& GetPoint(const Point& left,
const Point& right,
bool is_for_left) {
RTC_DCHECK_LE(left.x, right.x);
RTC_DCHECK_GE(left.y, right.y);
// Same X-value or Y-value triggers merging both points to the
// lower and/or left of the two points, respectively.
if (left.x == right.x) {
return right;
} else if (left.y == right.y) {
return left;
}
// If unmerged, boolean flag determines which of the points is desired.
return is_for_left ? left : right;
}
const Point a;
const Point b;
const float slope;
const float offset;
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_UTIL_THRESHOLD_CURVE_H_

View File

@ -0,0 +1,632 @@
/*
* Copyright (c) 2017 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/audio_network_adaptor/util/threshold_curve.h"
#include <memory>
#include "test/gtest.h"
// A threshold curve divides 2D space into three domains - below, on and above
// the threshold curve.
// The curve is defined by two points. Those points, P1 and P2, are ordered so
// that (P1.x <= P2.x && P1.y >= P2.y).
// The part of the curve which is between the two points is hereon referred
// to as the "segment".
// A "ray" extends from P1 directly upwards into infinity; that's the "vertical
// ray". Likewise, a "horizontal ray" extends from P2 directly rightwards.
//
// ^ | //
// | | vertical ray //
// | | //
// | | //
// | P1| //
// | \ //
// | \ segment //
// | \ //
// | \ horizontal ray //
// | P2 ------------------ //
// *---------------------------> //
namespace webrtc {
namespace {
enum RelativePosition { kBelow, kOn, kAbove };
void CheckRelativePosition(const ThresholdCurve& curve,
ThresholdCurve::Point point,
RelativePosition pos) {
RTC_CHECK(pos == kBelow || pos == kOn || pos == kAbove);
EXPECT_EQ(pos == kBelow, curve.IsBelowCurve(point));
EXPECT_EQ(pos == kAbove, curve.IsAboveCurve(point));
}
} // namespace
// Test that the curve correctly reports the below/above position of points,
// when the curve is a "normal" one - P1 and P2 are different in both their
// X and Y values.
TEST(ThresholdCurveTest, PointPositionToCommonCurve) {
// The points (P1-P2) define the curve. //
// All other points are above/below/on the curve. //
// //
// ^ //
// | | //
// | A F J R V //
// | | //
// | B P1 K S W //
// | \ //
// | \ //
// | \ L //
// | \ //
// | C G M T X //
// | \ //
// | N \ //
// | \ //
// | D H O P2--Y---------------- //
// | E I Q U Z //
// *----------------------------------> //
constexpr ThresholdCurve::Point p1{1000, 2000};
constexpr ThresholdCurve::Point p2{2000, 1000};
RTC_CHECK_GT((p1.x + p2.x) / 2, p1.x);
RTC_CHECK_LT((p1.x + p2.x) / 2, p2.x);
RTC_CHECK_LT((p1.y + p2.y) / 2, p1.y);
RTC_CHECK_GT((p1.y + p2.y) / 2, p2.y);
const ThresholdCurve curve(p1, p2);
{
// All cases where the point lies to the left of P1.
constexpr float x = p1.x - 1;
CheckRelativePosition(curve, {x, p1.y + 1}, kBelow); // A
CheckRelativePosition(curve, {x, p1.y + 0}, kBelow); // B
CheckRelativePosition(curve, {x, (p1.y + p2.y) / 2}, kBelow); // C
CheckRelativePosition(curve, {x, p2.y + 0}, kBelow); // D
CheckRelativePosition(curve, {x, p2.y - 1}, kBelow); // E
}
{
// All cases where the point has the same x-value as P1.
constexpr float x = p1.x;
CheckRelativePosition(curve, {x, p1.y + 1}, kOn); // F
CheckRelativePosition(curve, {x, p1.y + 0}, kOn); // P1
CheckRelativePosition(curve, {x, (p1.y + p2.y) / 2}, kBelow); // G
CheckRelativePosition(curve, {x, p2.y + 0}, kBelow); // H
CheckRelativePosition(curve, {x, p2.y - 1}, kBelow); // I
}
{
// To make sure we're really covering all of the cases, make sure that P1
// and P2 were chosen so that L would really be below K, and O would really
// be below N. (This would not hold if the Y values are too close together.)
RTC_CHECK_LT(((p1.y + p2.y) / 2) + 1, p1.y);
RTC_CHECK_LT(p2.y, ((p1.y + p2.y) / 2) - 1);
// All cases where the point's x-value is between P1 and P2.
constexpr float x = (p1.x + p2.x) / 2;
CheckRelativePosition(curve, {x, p1.y + 1}, kAbove); // J
CheckRelativePosition(curve, {x, p1.y + 0}, kAbove); // K
CheckRelativePosition(curve, {x, ((p1.y + p2.y) / 2) + 1}, kAbove); // L
CheckRelativePosition(curve, {x, (p1.y + p2.y) / 2}, kOn); // M
CheckRelativePosition(curve, {x, ((p1.y + p2.y) / 2) - 1}, kBelow); // N
CheckRelativePosition(curve, {x, p2.y + 0}, kBelow); // O
CheckRelativePosition(curve, {x, p2.y - 1}, kBelow); // Q
}
{
// All cases where the point has the same x-value as P2.
constexpr float x = p2.x;
CheckRelativePosition(curve, {x, p1.y + 1}, kAbove); // R
CheckRelativePosition(curve, {x, p1.y + 0}, kAbove); // S
CheckRelativePosition(curve, {x, (p1.y + p2.y) / 2}, kAbove); // T
CheckRelativePosition(curve, {x, p2.y + 0}, kOn); // P2
CheckRelativePosition(curve, {x, p2.y - 1}, kBelow); // U
}
{
// All cases where the point lies to the right of P2.
constexpr float x = p2.x + 1;
CheckRelativePosition(curve, {x, p1.y + 1}, kAbove); // V
CheckRelativePosition(curve, {x, p1.y + 0}, kAbove); // W
CheckRelativePosition(curve, {x, (p1.y + p2.y) / 2}, kAbove); // X
CheckRelativePosition(curve, {x, p2.y + 0}, kOn); // Y
CheckRelativePosition(curve, {x, p2.y - 1}, kBelow); // Z
}
}
// Test that the curve correctly reports the below/above position of points,
// when the curve is defined by two points with the same Y value.
TEST(ThresholdCurveTest, PointPositionToCurveWithHorizaontalSegment) {
// The points (P1-P2) define the curve.
// All other points are above/below/on the curve.
//
// ^
// | |
// | |
// | A D F I K
// | |
// | |
// | B P1--G--P2-L--
// | C E H J M
// *------------------>
constexpr ThresholdCurve::Point p1{100, 200};
constexpr ThresholdCurve::Point p2{p1.x + 1, p1.y};
RTC_CHECK_GT((p1.x + p2.x) / 2, p1.x);
RTC_CHECK_LT((p1.x + p2.x) / 2, p2.x);
const ThresholdCurve curve(p1, p2);
{
// All cases where the point lies to the left of P1.
constexpr float x = p1.x - 1;
CheckRelativePosition(curve, {x, p1.y + 1}, kBelow); // A
CheckRelativePosition(curve, {x, p1.y + 0}, kBelow); // B
CheckRelativePosition(curve, {x, p1.y - 1}, kBelow); // C
}
{
// All cases where the point has the same x-value as P1.
constexpr float x = p1.x;
CheckRelativePosition(curve, {x, p1.y + 1}, kOn); // D
CheckRelativePosition(curve, {x, p1.y + 0}, kOn); // P1
CheckRelativePosition(curve, {x, p1.y - 1}, kBelow); // E
}
{
// All cases where the point's x-value is between P1 and P2.
constexpr float x = (p1.x + p2.x) / 2;
CheckRelativePosition(curve, {x, p1.y + 1}, kAbove); // F
CheckRelativePosition(curve, {x, p1.y + 0}, kOn); // G
CheckRelativePosition(curve, {x, p1.y - 1}, kBelow); // H
}
{
// All cases where the point has the same x-value as P2.
constexpr float x = p2.x;
CheckRelativePosition(curve, {x, p1.y + 1}, kAbove); // I
CheckRelativePosition(curve, {x, p1.y + 0}, kOn); // P2
CheckRelativePosition(curve, {x, p1.y - 1}, kBelow); // J
}
{
// All cases where the point lies to the right of P2.
constexpr float x = p2.x + 1;
CheckRelativePosition(curve, {x, p1.y + 1}, kAbove); // K
CheckRelativePosition(curve, {x, p1.y + 0}, kOn); // L
CheckRelativePosition(curve, {x, p1.y - 1}, kBelow); // M
}
}
// Test that the curve correctly reports the below/above position of points,
// when the curve is defined by two points with the same X value.
TEST(ThresholdCurveTest, PointPositionToCurveWithVerticalSegment) {
// The points (P1-P2) define the curve.
// All other points are above/below/on the curve.
//
// ^
// | |
// | A B C
// | |
// | D P1 E
// | |
// | F G H
// | |
// | I P2--J------
// | K L M
// *------------------>
constexpr ThresholdCurve::Point p1{100, 200};
constexpr ThresholdCurve::Point p2{p1.x, p1.y - 1};
constexpr float left = p1.x - 1;
constexpr float on = p1.x;
constexpr float right = p1.x + 1;
RTC_CHECK_LT((p1.y + p2.y) / 2, p1.y);
RTC_CHECK_GT((p1.y + p2.y) / 2, p2.y);
const ThresholdCurve curve(p1, p2);
{
// All cases where the point lies above P1.
constexpr float y = p1.y + 1;
CheckRelativePosition(curve, {left, y}, kBelow); // A
CheckRelativePosition(curve, {on, y}, kOn); // B
CheckRelativePosition(curve, {right, y}, kAbove); // C
}
{
// All cases where the point has the same y-value as P1.
constexpr float y = p1.y;
CheckRelativePosition(curve, {left, y}, kBelow); // D
CheckRelativePosition(curve, {on, y}, kOn); // P1
CheckRelativePosition(curve, {right, y}, kAbove); // E
}
{
// All cases where the point's y-value is between P1 and P2.
constexpr float y = (p1.y + p2.y) / 2;
CheckRelativePosition(curve, {left, y}, kBelow); // F
CheckRelativePosition(curve, {on, y}, kOn); // G
CheckRelativePosition(curve, {right, y}, kAbove); // H
}
{
// All cases where the point has the same y-value as P2.
constexpr float y = p2.y;
CheckRelativePosition(curve, {left, y}, kBelow); // I
CheckRelativePosition(curve, {on, y}, kOn); // P2
CheckRelativePosition(curve, {right, y}, kOn); // J
}
{
// All cases where the point lies below P2.
constexpr float y = p2.y - 1;
CheckRelativePosition(curve, {left, y}, kBelow); // K
CheckRelativePosition(curve, {on, y}, kBelow); // L
CheckRelativePosition(curve, {right, y}, kBelow); // M
}
}
// Test that the curve correctly reports the below/above position of points,
// when the curve is defined by two points which are identical.
TEST(ThresholdCurveTest, PointPositionCurveWithNullSegment) {
// The points (P1-P2) define the curve.
// All other points are above/below/on the curve.
//
// ^
// | |
// | A D F
// | |
// | B P---G------
// | C E H
// *------------------>
constexpr ThresholdCurve::Point p{100, 200};
const ThresholdCurve curve(p, p);
{
// All cases where the point lies to the left of P.
constexpr float x = p.x - 1;
CheckRelativePosition(curve, {x, p.y + 1}, kBelow); // A
CheckRelativePosition(curve, {x, p.y + 0}, kBelow); // B
CheckRelativePosition(curve, {x, p.y - 1}, kBelow); // C
}
{
// All cases where the point has the same x-value as P.
constexpr float x = p.x + 0;
CheckRelativePosition(curve, {x, p.y + 1}, kOn); // D
CheckRelativePosition(curve, {x, p.y + 0}, kOn); // P
CheckRelativePosition(curve, {x, p.y - 1}, kBelow); // E
}
{
// All cases where the point lies to the right of P.
constexpr float x = p.x + 1;
CheckRelativePosition(curve, {x, p.y + 1}, kAbove); // F
CheckRelativePosition(curve, {x, p.y + 0}, kOn); // G
CheckRelativePosition(curve, {x, p.y - 1}, kBelow); // H
}
}
// Test that the relative position of two curves is computed correctly when
// the two curves have the same projection on the X-axis.
TEST(ThresholdCurveTest, TwoCurvesSegmentHasSameProjectionAxisX) {
// ^ //
// | C1 + C2 //
// | | //
// | |\ //
// | | \ //
// | \ \ //
// | \ \ //
// | \ \ //
// | \ -------- C2 //
// | --------- C1 //
// *---------------------> //
constexpr ThresholdCurve::Point c1_left{5, 10};
constexpr ThresholdCurve::Point c1_right{10, 5};
const ThresholdCurve c1_curve(c1_left, c1_right);
// Same x-values, but higher on Y. (Can be parallel, but doesn't have to be.)
constexpr ThresholdCurve::Point c2_left{c1_left.x, c1_left.y + 20};
constexpr ThresholdCurve::Point c2_right{c1_right.x, c1_right.y + 10};
const ThresholdCurve c2_curve(c2_left, c2_right);
EXPECT_TRUE(c1_curve <= c2_curve);
EXPECT_FALSE(c2_curve <= c1_curve);
}
// Test that the relative position of two curves is computed correctly when
// the higher curve's projection on the X-axis is a strict subset of the
// lower curve's projection on the X-axis (on both ends).
TEST(ThresholdCurveTest, TwoCurvesSegmentOfHigherSubsetProjectionAxisX) {
// ^ //
// | C1 C2 //
// | | | //
// | | | //
// | \ | //
// | \ | //
// | \ \ //
// | \ \ //
// | \ --------- C2 //
// | \ //
// | \ //
// | ---------C1 //
// *---------------------> //
constexpr ThresholdCurve::Point c1_left{5, 10};
constexpr ThresholdCurve::Point c1_right{10, 5};
const ThresholdCurve c1_curve(c1_left, c1_right);
constexpr ThresholdCurve::Point c2_left{6, 11};
constexpr ThresholdCurve::Point c2_right{9, 7};
const ThresholdCurve c2_curve(c2_left, c2_right);
EXPECT_TRUE(c1_curve <= c2_curve);
EXPECT_FALSE(c2_curve <= c1_curve);
}
// Test that the relative position of two curves is computed correctly when
// the higher curve's right point is above lower curve's horizontal ray (meaning
// the higher curve's projection on the X-axis extends further right than
// the lower curve's).
TEST(ThresholdCurveTest,
TwoCurvesRightPointOfHigherCurveAboveHorizontalRayOfLower) {
// ^ //
// | C1 + C2 //
// | | //
// | |\ //
// | | \ //
// | | \ //
// | | \ //
// | | \ //
// | \ \ //
// | \ \ //
// | \ \ //
// | \ ----- C2 //
// | --------- C1 //
// *---------------------> //
constexpr ThresholdCurve::Point c1_left{5, 10};
constexpr ThresholdCurve::Point c1_right{10, 5};
const ThresholdCurve c1_curve(c1_left, c1_right);
constexpr ThresholdCurve::Point c2_left{c1_left.x, c1_left.y + 1};
constexpr ThresholdCurve::Point c2_right{c1_right.x + 1, c1_right.y + 1};
const ThresholdCurve c2_curve(c2_left, c2_right);
EXPECT_TRUE(c1_curve <= c2_curve);
EXPECT_FALSE(c2_curve <= c1_curve);
}
// Test that the relative position of two curves is computed correctly when
// the higher curve's points are on the lower curve's rays (left point on the
// veritcal ray, right point on the horizontal ray).
TEST(ThresholdCurveTest, TwoCurvesPointsOfHigherOnRaysOfLower) {
// ^
// | C1 + C2 //
// | | //
// | |\ //
// | | \ //
// | \ \ //
// | \ \ //
// | \ \ //
// | \ \ //
// | ----- C1 + C2 //
// *---------------------> //
constexpr ThresholdCurve::Point c1_left{5, 10};
constexpr ThresholdCurve::Point c1_right{10, 5};
const ThresholdCurve c1_curve(c1_left, c1_right);
// Same x-values, but one of the points is higher on Y (the other isn't).
constexpr ThresholdCurve::Point c2_left{c1_left.x, c1_left.y + 2};
constexpr ThresholdCurve::Point c2_right{c1_right.x + 3, c1_right.y};
const ThresholdCurve c2_curve(c2_left, c2_right);
EXPECT_TRUE(c1_curve <= c2_curve);
EXPECT_FALSE(c2_curve <= c1_curve);
}
// Test that the relative position of two curves is computed correctly when
// the second curve's segment intersects the first curve's vertical ray.
TEST(ThresholdCurveTest, SecondCurveCrossesVerticalRayOfFirstCurve) {
// ^ //
// | C2 C1 //
// | | | //
// | \| //
// | | //
// | |\ //
// | | \ //
// | \ \ //
// | \ \ //
// | \ \ //
// | \ ------- C2 //
// | -------- C1 //
// *---------------------> //
constexpr ThresholdCurve::Point c1_left{5, 10};
constexpr ThresholdCurve::Point c1_right{10, 5};
const ThresholdCurve c1_curve(c1_left, c1_right);
constexpr ThresholdCurve::Point c2_left{c1_left.x - 1, c1_left.y + 1};
constexpr ThresholdCurve::Point c2_right{c1_right.x, c1_right.y + 1};
const ThresholdCurve c2_curve(c2_left, c2_right);
EXPECT_FALSE(c1_curve <= c2_curve);
EXPECT_FALSE(c2_curve <= c1_curve);
}
// Test that the relative position of two curves is computed correctly when
// the second curve's segment intersects the first curve's horizontal ray.
TEST(ThresholdCurveTest, SecondCurveCrossesHorizontalRayOfFirstCurve) {
// ^ //
// | C1 + C2 //
// | | //
// | |\ //
// | \ \ //
// | \ \ //
// | \ \ //
// | \ \ //
// | ----------- C1 //
// | \ //
// | ------- C2 //
// *--------------------> //
constexpr ThresholdCurve::Point c1_left{5, 10};
constexpr ThresholdCurve::Point c1_right{10, 5};
const ThresholdCurve c1_curve(c1_left, c1_right);
constexpr ThresholdCurve::Point c2_left{c1_left.x, c1_left.y + 1};
constexpr ThresholdCurve::Point c2_right{c1_right.x + 2, c1_right.y - 1};
const ThresholdCurve c2_curve(c2_left, c2_right);
EXPECT_FALSE(c1_curve <= c2_curve);
EXPECT_FALSE(c2_curve <= c1_curve);
}
// Test that the relative position of two curves is computed correctly when
// the second curve's segment intersects the first curve's segment.
TEST(ThresholdCurveTest, TwoCurvesWithCrossingSegments) {
// ^ //
// | C2 C1 //
// | | | //
// | | | //
// | | \ //
// | | \ //
// | -_ \ //
// | -_ \ //
// | -_\ //
// | -_ //
// | \-_ //
// | \ ---------- C2 //
// | ----------- C1 //
// | //
// | //
// *-------------------------> //
constexpr ThresholdCurve::Point c1_left{5, 10};
constexpr ThresholdCurve::Point c1_right{10, 5};
const ThresholdCurve c1_curve(c1_left, c1_right);
constexpr ThresholdCurve::Point c2_left{4, 9};
constexpr ThresholdCurve::Point c2_right{10, 6};
const ThresholdCurve c2_curve(c2_left, c2_right);
// The test is structured so that the two curves intersect at (8, 7).
RTC_CHECK(!c1_curve.IsAboveCurve({8, 7}));
RTC_CHECK(!c1_curve.IsBelowCurve({8, 7}));
RTC_CHECK(!c2_curve.IsAboveCurve({8, 7}));
RTC_CHECK(!c2_curve.IsBelowCurve({8, 7}));
EXPECT_FALSE(c1_curve <= c2_curve);
EXPECT_FALSE(c2_curve <= c1_curve);
}
// Test that the relative position of two curves is computed correctly when
// both curves are identical.
TEST(ThresholdCurveTest, IdenticalCurves) {
// ^ //
// | C1 + C2 //
// | | //
// | | //
// | \ //
// | \ //
// | \ //
// | ------- C1 + C2 //
// *---------------------> //
constexpr ThresholdCurve::Point left{5, 10};
constexpr ThresholdCurve::Point right{10, 5};
const ThresholdCurve c1_curve(left, right);
const ThresholdCurve c2_curve(left, right);
EXPECT_TRUE(c1_curve <= c2_curve);
EXPECT_TRUE(c2_curve <= c1_curve);
}
// Test that the relative position of two curves is computed correctly when
// they are "nearly identical" - the first curve's segment is contained within
// the second curve's segment, but the second curve's segment extends further
// to the left (which also produces separate vertical rays for the curves).
TEST(ThresholdCurveTest, NearlyIdenticalCurvesSecondContinuesOnOtherLeftSide) {
// ^ //
// | C2 C1 //
// | | | //
// | | | //
// | \| //
// | | //
// | \ //
// | \ //
// | \ //
// | ----- C1 + C2 //
// *---------------------> //
constexpr ThresholdCurve::Point c1_left{5, 10};
constexpr ThresholdCurve::Point c1_right{10, 5};
const ThresholdCurve c1_curve(c1_left, c1_left);
constexpr ThresholdCurve::Point c2_left{c1_left.x - 1, c1_left.y + 1};
constexpr ThresholdCurve::Point c2_right = c1_right;
const ThresholdCurve c2_curve(c2_left, c2_right);
EXPECT_FALSE(c1_curve <= c2_curve);
EXPECT_TRUE(c2_curve <= c1_curve);
}
// Test that the relative position of two curves is computed correctly when
// they are "nearly identical" - the first curve's segment is contained within
// the second curve's segment, but the second curve's segment extends further
// to the right (which also produces separate horizontal rays for the curves).
TEST(ThresholdCurveTest, NearlyIdenticalCurvesSecondContinuesOnOtherRightSide) {
// ^ //
// | C1 + C2 //
// | | //
// | | //
// | \ //
// | \ //
// | \ //
// | \----------- C1 //
// | \ //
// | ---------- C2 //
// *---------------------> //
constexpr ThresholdCurve::Point c1_left{5, 10};
constexpr ThresholdCurve::Point c1_right{10, 5};
const ThresholdCurve c1_curve(c1_left, c1_left);
constexpr ThresholdCurve::Point c2_left = c1_left;
constexpr ThresholdCurve::Point c2_right{c1_right.x + 1, c1_right.y - 1};
const ThresholdCurve c2_curve(c2_left, c2_right);
EXPECT_FALSE(c1_curve <= c2_curve);
EXPECT_TRUE(c2_curve <= c1_curve);
}
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
// The higher-left point must be given as the first point, and the lower-right
// point must be given as the second.
// This necessarily produces a non-positive slope.
TEST(ThresholdCurveTest, WrongOrderPoints) {
std::unique_ptr<ThresholdCurve> curve;
constexpr ThresholdCurve::Point left{5, 10};
constexpr ThresholdCurve::Point right{10, 5};
EXPECT_DEATH(curve.reset(new ThresholdCurve(right, left)), "");
}
#endif
} // namespace webrtc

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
// This file is for backwards compatibility only! Use
// webrtc/api/audio_codecs/audio_decoder.h instead!
// TODO(kwiberg): Remove it.
#ifndef MODULES_AUDIO_CODING_CODECS_AUDIO_DECODER_H_
#define MODULES_AUDIO_CODING_CODECS_AUDIO_DECODER_H_
#include "api/audio_codecs/audio_decoder.h"
#endif // MODULES_AUDIO_CODING_CODECS_AUDIO_DECODER_H_

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
// This file is for backwards compatibility only! Use
// webrtc/api/audio_codecs/audio_encoder.h instead!
// TODO(ossu): Remove it.
#ifndef MODULES_AUDIO_CODING_CODECS_AUDIO_ENCODER_H_
#define MODULES_AUDIO_CODING_CODECS_AUDIO_ENCODER_H_
#include "api/audio_codecs/audio_encoder.h"
#endif // MODULES_AUDIO_CODING_CODECS_AUDIO_ENCODER_H_

View File

@ -0,0 +1,168 @@
/*
* 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 "api/audio_codecs/builtin_audio_decoder_factory.h"
#include <memory>
#include "test/gtest.h"
namespace webrtc {
TEST(AudioDecoderFactoryTest, CreateUnknownDecoder) {
rtc::scoped_refptr<AudioDecoderFactory> adf =
CreateBuiltinAudioDecoderFactory();
ASSERT_TRUE(adf);
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("rey", 8000, 1), absl::nullopt));
}
TEST(AudioDecoderFactoryTest, CreatePcmu) {
rtc::scoped_refptr<AudioDecoderFactory> adf =
CreateBuiltinAudioDecoderFactory();
ASSERT_TRUE(adf);
// PCMu supports 8 kHz, and any number of channels.
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("pcmu", 8000, 0), absl::nullopt));
EXPECT_TRUE(
adf->MakeAudioDecoder(SdpAudioFormat("pcmu", 8000, 1), absl::nullopt));
EXPECT_TRUE(
adf->MakeAudioDecoder(SdpAudioFormat("pcmu", 8000, 2), absl::nullopt));
EXPECT_TRUE(
adf->MakeAudioDecoder(SdpAudioFormat("pcmu", 8000, 3), absl::nullopt));
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("pcmu", 16000, 1), absl::nullopt));
}
TEST(AudioDecoderFactoryTest, CreatePcma) {
rtc::scoped_refptr<AudioDecoderFactory> adf =
CreateBuiltinAudioDecoderFactory();
ASSERT_TRUE(adf);
// PCMa supports 8 kHz, and any number of channels.
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("pcma", 8000, 0), absl::nullopt));
EXPECT_TRUE(
adf->MakeAudioDecoder(SdpAudioFormat("pcma", 8000, 1), absl::nullopt));
EXPECT_TRUE(
adf->MakeAudioDecoder(SdpAudioFormat("pcma", 8000, 2), absl::nullopt));
EXPECT_TRUE(
adf->MakeAudioDecoder(SdpAudioFormat("pcma", 8000, 3), absl::nullopt));
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("pcma", 16000, 1), absl::nullopt));
}
TEST(AudioDecoderFactoryTest, CreateIlbc) {
rtc::scoped_refptr<AudioDecoderFactory> adf =
CreateBuiltinAudioDecoderFactory();
ASSERT_TRUE(adf);
// iLBC supports 8 kHz, 1 channel.
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("ilbc", 8000, 0), absl::nullopt));
#ifdef WEBRTC_CODEC_ILBC
EXPECT_TRUE(
adf->MakeAudioDecoder(SdpAudioFormat("ilbc", 8000, 1), absl::nullopt));
#endif
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("ilbc", 8000, 2), absl::nullopt));
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("ilbc", 16000, 1), absl::nullopt));
}
TEST(AudioDecoderFactoryTest, CreateIsac) {
rtc::scoped_refptr<AudioDecoderFactory> adf =
CreateBuiltinAudioDecoderFactory();
ASSERT_TRUE(adf);
// iSAC supports 16 kHz, 1 channel. The float implementation additionally
// supports 32 kHz, 1 channel.
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("isac", 16000, 0), absl::nullopt));
EXPECT_TRUE(
adf->MakeAudioDecoder(SdpAudioFormat("isac", 16000, 1), absl::nullopt));
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("isac", 16000, 2), absl::nullopt));
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("isac", 8000, 1), absl::nullopt));
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("isac", 48000, 1), absl::nullopt));
#ifdef WEBRTC_ARCH_ARM
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("isac", 32000, 1), absl::nullopt));
#else
EXPECT_TRUE(
adf->MakeAudioDecoder(SdpAudioFormat("isac", 32000, 1), absl::nullopt));
#endif
}
TEST(AudioDecoderFactoryTest, CreateL16) {
rtc::scoped_refptr<AudioDecoderFactory> adf =
CreateBuiltinAudioDecoderFactory();
ASSERT_TRUE(adf);
// L16 supports any clock rate, any number of channels.
const int clockrates[] = {8000, 16000, 32000, 48000};
const int num_channels[] = {1, 2, 3, 4711};
for (int clockrate : clockrates) {
EXPECT_FALSE(adf->MakeAudioDecoder(SdpAudioFormat("l16", clockrate, 0),
absl::nullopt));
for (int channels : num_channels) {
EXPECT_TRUE(adf->MakeAudioDecoder(
SdpAudioFormat("l16", clockrate, channels), absl::nullopt));
}
}
}
TEST(AudioDecoderFactoryTest, CreateG722) {
rtc::scoped_refptr<AudioDecoderFactory> adf =
CreateBuiltinAudioDecoderFactory();
ASSERT_TRUE(adf);
// g722 supports 8 kHz, 1-2 channels.
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("g722", 8000, 0), absl::nullopt));
EXPECT_TRUE(
adf->MakeAudioDecoder(SdpAudioFormat("g722", 8000, 1), absl::nullopt));
EXPECT_TRUE(
adf->MakeAudioDecoder(SdpAudioFormat("g722", 8000, 2), absl::nullopt));
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("g722", 8000, 3), absl::nullopt));
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("g722", 16000, 1), absl::nullopt));
EXPECT_FALSE(
adf->MakeAudioDecoder(SdpAudioFormat("g722", 32000, 1), absl::nullopt));
// g722 actually uses a 16 kHz sample rate instead of the nominal 8 kHz.
std::unique_ptr<AudioDecoder> dec =
adf->MakeAudioDecoder(SdpAudioFormat("g722", 8000, 1), absl::nullopt);
EXPECT_EQ(16000, dec->SampleRateHz());
}
TEST(AudioDecoderFactoryTest, CreateOpus) {
rtc::scoped_refptr<AudioDecoderFactory> adf =
CreateBuiltinAudioDecoderFactory();
ASSERT_TRUE(adf);
// Opus supports 48 kHz, 2 channels, and wants a "stereo" parameter whose
// value is either "0" or "1".
for (int hz : {8000, 16000, 32000, 48000}) {
for (int channels : {0, 1, 2, 3}) {
for (std::string stereo : {"XX", "0", "1", "2"}) {
std::map<std::string, std::string> params;
if (stereo != "XX") {
params["stereo"] = stereo;
}
const bool good = (hz == 48000 && channels == 2 &&
(stereo == "XX" || stereo == "0" || stereo == "1"));
EXPECT_EQ(good,
static_cast<bool>(adf->MakeAudioDecoder(
SdpAudioFormat("opus", hz, channels, std::move(params)),
absl::nullopt)));
}
}
}
}
} // namespace webrtc

View File

@ -0,0 +1,147 @@
/*
* Copyright (c) 2017 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 "api/audio_codecs/builtin_audio_encoder_factory.h"
#include <limits>
#include <memory>
#include <vector>
#include "rtc_base/numerics/safe_conversions.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
class AudioEncoderFactoryTest
: public ::testing::TestWithParam<rtc::scoped_refptr<AudioEncoderFactory>> {
};
TEST_P(AudioEncoderFactoryTest, SupportsAtLeastOneFormat) {
auto factory = GetParam();
auto supported_encoders = factory->GetSupportedEncoders();
EXPECT_FALSE(supported_encoders.empty());
}
TEST_P(AudioEncoderFactoryTest, CanQueryAllSupportedFormats) {
auto factory = GetParam();
auto supported_encoders = factory->GetSupportedEncoders();
for (const auto& spec : supported_encoders) {
auto info = factory->QueryAudioEncoder(spec.format);
EXPECT_TRUE(info);
}
}
TEST_P(AudioEncoderFactoryTest, CanConstructAllSupportedEncoders) {
auto factory = GetParam();
auto supported_encoders = factory->GetSupportedEncoders();
for (const auto& spec : supported_encoders) {
auto info = factory->QueryAudioEncoder(spec.format);
auto encoder = factory->MakeAudioEncoder(127, spec.format, absl::nullopt);
EXPECT_TRUE(encoder);
EXPECT_EQ(encoder->SampleRateHz(), info->sample_rate_hz);
EXPECT_EQ(encoder->NumChannels(), info->num_channels);
EXPECT_EQ(encoder->RtpTimestampRateHz(), spec.format.clockrate_hz);
}
}
TEST_P(AudioEncoderFactoryTest, CanRunAllSupportedEncoders) {
constexpr int kTestPayloadType = 127;
auto factory = GetParam();
auto supported_encoders = factory->GetSupportedEncoders();
for (const auto& spec : supported_encoders) {
auto encoder =
factory->MakeAudioEncoder(kTestPayloadType, spec.format, absl::nullopt);
EXPECT_TRUE(encoder);
encoder->Reset();
const int num_samples = rtc::checked_cast<int>(
encoder->SampleRateHz() * encoder->NumChannels() / 100);
rtc::Buffer out;
rtc::BufferT<int16_t> audio;
audio.SetData(num_samples, [](rtc::ArrayView<int16_t> audio) {
for (size_t i = 0; i != audio.size(); ++i) {
// Just put some numbers in there, ensure they're within range.
audio[i] =
static_cast<int16_t>(i & std::numeric_limits<int16_t>::max());
}
return audio.size();
});
// This is here to stop the test going forever with a broken encoder.
constexpr int kMaxEncodeCalls = 100;
int blocks = 0;
for (; blocks < kMaxEncodeCalls; ++blocks) {
AudioEncoder::EncodedInfo info = encoder->Encode(
blocks * encoder->RtpTimestampRateHz() / 100, audio, &out);
EXPECT_EQ(info.encoded_bytes, out.size());
if (info.encoded_bytes > 0) {
EXPECT_EQ(0u, info.encoded_timestamp);
EXPECT_EQ(kTestPayloadType, info.payload_type);
break;
}
}
ASSERT_LT(blocks, kMaxEncodeCalls);
const unsigned int next_timestamp =
blocks * encoder->RtpTimestampRateHz() / 100;
out.Clear();
for (; blocks < kMaxEncodeCalls; ++blocks) {
AudioEncoder::EncodedInfo info = encoder->Encode(
blocks * encoder->RtpTimestampRateHz() / 100, audio, &out);
EXPECT_EQ(info.encoded_bytes, out.size());
if (info.encoded_bytes > 0) {
EXPECT_EQ(next_timestamp, info.encoded_timestamp);
EXPECT_EQ(kTestPayloadType, info.payload_type);
break;
}
}
ASSERT_LT(blocks, kMaxEncodeCalls);
}
}
INSTANTIATE_TEST_SUITE_P(BuiltinAudioEncoderFactoryTest,
AudioEncoderFactoryTest,
::testing::Values(CreateBuiltinAudioEncoderFactory()));
TEST(BuiltinAudioEncoderFactoryTest, SupportsTheExpectedFormats) {
using ::testing::ElementsAreArray;
// Check that we claim to support the formats we expect from build flags, and
// we've ordered them correctly.
auto factory = CreateBuiltinAudioEncoderFactory();
auto specs = factory->GetSupportedEncoders();
const std::vector<SdpAudioFormat> supported_formats = [&specs] {
std::vector<SdpAudioFormat> formats;
formats.reserve(specs.size());
for (const auto& spec : specs) {
formats.push_back(spec.format);
}
return formats;
}();
const std::vector<SdpAudioFormat> expected_formats = {
#ifdef WEBRTC_CODEC_OPUS
{"opus", 48000, 2, {{"minptime", "10"}, {"useinbandfec", "1"}}},
#endif
#if defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)
{"isac", 16000, 1},
#endif
#ifdef WEBRTC_CODEC_ISAC
{"isac", 32000, 1},
#endif
{"G722", 8000, 1},
#ifdef WEBRTC_CODEC_ILBC
{"ilbc", 8000, 1},
#endif
{"pcmu", 8000, 1},
{"pcma", 8000, 1}
};
ASSERT_THAT(supported_formats, ElementsAreArray(expected_formats));
}
} // namespace webrtc

View File

@ -0,0 +1,323 @@
/*
* Copyright (c) 2014 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/codecs/cng/audio_encoder_cng.h"
#include <cstdint>
#include <memory>
#include <utility>
#include "absl/types/optional.h"
#include "api/units/time_delta.h"
#include "modules/audio_coding/codecs/cng/webrtc_cng.h"
#include "rtc_base/checks.h"
namespace webrtc {
namespace {
const int kMaxFrameSizeMs = 60;
class AudioEncoderCng final : public AudioEncoder {
public:
explicit AudioEncoderCng(AudioEncoderCngConfig&& config);
~AudioEncoderCng() override;
// Not copyable or moveable.
AudioEncoderCng(const AudioEncoderCng&) = delete;
AudioEncoderCng(AudioEncoderCng&&) = delete;
AudioEncoderCng& operator=(const AudioEncoderCng&) = delete;
AudioEncoderCng& operator=(AudioEncoderCng&&) = delete;
int SampleRateHz() const override;
size_t NumChannels() const override;
int RtpTimestampRateHz() const override;
size_t Num10MsFramesInNextPacket() const override;
size_t Max10MsFramesInAPacket() const override;
int GetTargetBitrate() const override;
EncodedInfo EncodeImpl(uint32_t rtp_timestamp,
rtc::ArrayView<const int16_t> audio,
rtc::Buffer* encoded) override;
void Reset() override;
bool SetFec(bool enable) override;
bool SetDtx(bool enable) override;
bool SetApplication(Application application) override;
void SetMaxPlaybackRate(int frequency_hz) override;
rtc::ArrayView<std::unique_ptr<AudioEncoder>> ReclaimContainedEncoders()
override;
void OnReceivedUplinkPacketLossFraction(
float uplink_packet_loss_fraction) override;
void OnReceivedUplinkBandwidth(
int target_audio_bitrate_bps,
absl::optional<int64_t> bwe_period_ms) override;
absl::optional<std::pair<TimeDelta, TimeDelta>> GetFrameLengthRange()
const override;
private:
EncodedInfo EncodePassive(size_t frames_to_encode, rtc::Buffer* encoded);
EncodedInfo EncodeActive(size_t frames_to_encode, rtc::Buffer* encoded);
size_t SamplesPer10msFrame() const;
std::unique_ptr<AudioEncoder> speech_encoder_;
const int cng_payload_type_;
const int num_cng_coefficients_;
const int sid_frame_interval_ms_;
std::vector<int16_t> speech_buffer_;
std::vector<uint32_t> rtp_timestamps_;
bool last_frame_active_;
std::unique_ptr<Vad> vad_;
std::unique_ptr<ComfortNoiseEncoder> cng_encoder_;
};
AudioEncoderCng::AudioEncoderCng(AudioEncoderCngConfig&& config)
: speech_encoder_((static_cast<void>([&] {
RTC_CHECK(config.IsOk()) << "Invalid configuration.";
}()),
std::move(config.speech_encoder))),
cng_payload_type_(config.payload_type),
num_cng_coefficients_(config.num_cng_coefficients),
sid_frame_interval_ms_(config.sid_frame_interval_ms),
last_frame_active_(true),
vad_(config.vad ? std::unique_ptr<Vad>(config.vad)
: CreateVad(config.vad_mode)),
cng_encoder_(new ComfortNoiseEncoder(SampleRateHz(),
sid_frame_interval_ms_,
num_cng_coefficients_)) {}
AudioEncoderCng::~AudioEncoderCng() = default;
int AudioEncoderCng::SampleRateHz() const {
return speech_encoder_->SampleRateHz();
}
size_t AudioEncoderCng::NumChannels() const {
return 1;
}
int AudioEncoderCng::RtpTimestampRateHz() const {
return speech_encoder_->RtpTimestampRateHz();
}
size_t AudioEncoderCng::Num10MsFramesInNextPacket() const {
return speech_encoder_->Num10MsFramesInNextPacket();
}
size_t AudioEncoderCng::Max10MsFramesInAPacket() const {
return speech_encoder_->Max10MsFramesInAPacket();
}
int AudioEncoderCng::GetTargetBitrate() const {
return speech_encoder_->GetTargetBitrate();
}
AudioEncoder::EncodedInfo AudioEncoderCng::EncodeImpl(
uint32_t rtp_timestamp,
rtc::ArrayView<const int16_t> audio,
rtc::Buffer* encoded) {
const size_t samples_per_10ms_frame = SamplesPer10msFrame();
RTC_CHECK_EQ(speech_buffer_.size(),
rtp_timestamps_.size() * samples_per_10ms_frame);
rtp_timestamps_.push_back(rtp_timestamp);
RTC_DCHECK_EQ(samples_per_10ms_frame, audio.size());
speech_buffer_.insert(speech_buffer_.end(), audio.cbegin(), audio.cend());
const size_t frames_to_encode = speech_encoder_->Num10MsFramesInNextPacket();
if (rtp_timestamps_.size() < frames_to_encode) {
return EncodedInfo();
}
RTC_CHECK_LE(frames_to_encode * 10, kMaxFrameSizeMs)
<< "Frame size cannot be larger than " << kMaxFrameSizeMs
<< " ms when using VAD/CNG.";
// Group several 10 ms blocks per VAD call. Call VAD once or twice using the
// following split sizes:
// 10 ms = 10 + 0 ms; 20 ms = 20 + 0 ms; 30 ms = 30 + 0 ms;
// 40 ms = 20 + 20 ms; 50 ms = 30 + 20 ms; 60 ms = 30 + 30 ms.
size_t blocks_in_first_vad_call =
(frames_to_encode > 3 ? 3 : frames_to_encode);
if (frames_to_encode == 4)
blocks_in_first_vad_call = 2;
RTC_CHECK_GE(frames_to_encode, blocks_in_first_vad_call);
const size_t blocks_in_second_vad_call =
frames_to_encode - blocks_in_first_vad_call;
// Check if all of the buffer is passive speech. Start with checking the first
// block.
Vad::Activity activity = vad_->VoiceActivity(
&speech_buffer_[0], samples_per_10ms_frame * blocks_in_first_vad_call,
SampleRateHz());
if (activity == Vad::kPassive && blocks_in_second_vad_call > 0) {
// Only check the second block if the first was passive.
activity = vad_->VoiceActivity(
&speech_buffer_[samples_per_10ms_frame * blocks_in_first_vad_call],
samples_per_10ms_frame * blocks_in_second_vad_call, SampleRateHz());
}
EncodedInfo info;
switch (activity) {
case Vad::kPassive: {
info = EncodePassive(frames_to_encode, encoded);
last_frame_active_ = false;
break;
}
case Vad::kActive: {
info = EncodeActive(frames_to_encode, encoded);
last_frame_active_ = true;
break;
}
case Vad::kError: {
FATAL(); // Fails only if fed invalid data.
break;
}
}
speech_buffer_.erase(
speech_buffer_.begin(),
speech_buffer_.begin() + frames_to_encode * samples_per_10ms_frame);
rtp_timestamps_.erase(rtp_timestamps_.begin(),
rtp_timestamps_.begin() + frames_to_encode);
return info;
}
void AudioEncoderCng::Reset() {
speech_encoder_->Reset();
speech_buffer_.clear();
rtp_timestamps_.clear();
last_frame_active_ = true;
vad_->Reset();
cng_encoder_.reset(new ComfortNoiseEncoder(
SampleRateHz(), sid_frame_interval_ms_, num_cng_coefficients_));
}
bool AudioEncoderCng::SetFec(bool enable) {
return speech_encoder_->SetFec(enable);
}
bool AudioEncoderCng::SetDtx(bool enable) {
return speech_encoder_->SetDtx(enable);
}
bool AudioEncoderCng::SetApplication(Application application) {
return speech_encoder_->SetApplication(application);
}
void AudioEncoderCng::SetMaxPlaybackRate(int frequency_hz) {
speech_encoder_->SetMaxPlaybackRate(frequency_hz);
}
rtc::ArrayView<std::unique_ptr<AudioEncoder>>
AudioEncoderCng::ReclaimContainedEncoders() {
return rtc::ArrayView<std::unique_ptr<AudioEncoder>>(&speech_encoder_, 1);
}
void AudioEncoderCng::OnReceivedUplinkPacketLossFraction(
float uplink_packet_loss_fraction) {
speech_encoder_->OnReceivedUplinkPacketLossFraction(
uplink_packet_loss_fraction);
}
void AudioEncoderCng::OnReceivedUplinkBandwidth(
int target_audio_bitrate_bps,
absl::optional<int64_t> bwe_period_ms) {
speech_encoder_->OnReceivedUplinkBandwidth(target_audio_bitrate_bps,
bwe_period_ms);
}
absl::optional<std::pair<TimeDelta, TimeDelta>>
AudioEncoderCng::GetFrameLengthRange() const {
return speech_encoder_->GetFrameLengthRange();
}
AudioEncoder::EncodedInfo AudioEncoderCng::EncodePassive(
size_t frames_to_encode,
rtc::Buffer* encoded) {
bool force_sid = last_frame_active_;
bool output_produced = false;
const size_t samples_per_10ms_frame = SamplesPer10msFrame();
AudioEncoder::EncodedInfo info;
for (size_t i = 0; i < frames_to_encode; ++i) {
// It's important not to pass &info.encoded_bytes directly to
// WebRtcCng_Encode(), since later loop iterations may return zero in
// that value, in which case we don't want to overwrite any value from
// an earlier iteration.
size_t encoded_bytes_tmp =
cng_encoder_->Encode(rtc::ArrayView<const int16_t>(
&speech_buffer_[i * samples_per_10ms_frame],
samples_per_10ms_frame),
force_sid, encoded);
if (encoded_bytes_tmp > 0) {
RTC_CHECK(!output_produced);
info.encoded_bytes = encoded_bytes_tmp;
output_produced = true;
force_sid = false;
}
}
info.encoded_timestamp = rtp_timestamps_.front();
info.payload_type = cng_payload_type_;
info.send_even_if_empty = true;
info.speech = false;
return info;
}
AudioEncoder::EncodedInfo AudioEncoderCng::EncodeActive(size_t frames_to_encode,
rtc::Buffer* encoded) {
const size_t samples_per_10ms_frame = SamplesPer10msFrame();
AudioEncoder::EncodedInfo info;
for (size_t i = 0; i < frames_to_encode; ++i) {
info =
speech_encoder_->Encode(rtp_timestamps_.front(),
rtc::ArrayView<const int16_t>(
&speech_buffer_[i * samples_per_10ms_frame],
samples_per_10ms_frame),
encoded);
if (i + 1 == frames_to_encode) {
RTC_CHECK_GT(info.encoded_bytes, 0) << "Encoder didn't deliver data.";
} else {
RTC_CHECK_EQ(info.encoded_bytes, 0)
<< "Encoder delivered data too early.";
}
}
return info;
}
size_t AudioEncoderCng::SamplesPer10msFrame() const {
return rtc::CheckedDivExact(10 * SampleRateHz(), 1000);
}
} // namespace
AudioEncoderCngConfig::AudioEncoderCngConfig() = default;
AudioEncoderCngConfig::AudioEncoderCngConfig(AudioEncoderCngConfig&&) = default;
AudioEncoderCngConfig::~AudioEncoderCngConfig() = default;
bool AudioEncoderCngConfig::IsOk() const {
if (num_channels != 1)
return false;
if (!speech_encoder)
return false;
if (num_channels != speech_encoder->NumChannels())
return false;
if (sid_frame_interval_ms <
static_cast<int>(speech_encoder->Max10MsFramesInAPacket() * 10))
return false;
if (num_cng_coefficients > WEBRTC_CNG_MAX_LPC_ORDER ||
num_cng_coefficients <= 0)
return false;
return true;
}
std::unique_ptr<AudioEncoder> CreateComfortNoiseEncoder(
AudioEncoderCngConfig&& config) {
return std::make_unique<AudioEncoderCng>(std::move(config));
}
} // namespace webrtc

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2014 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.
*/
#ifndef MODULES_AUDIO_CODING_CODECS_CNG_AUDIO_ENCODER_CNG_H_
#define MODULES_AUDIO_CODING_CODECS_CNG_AUDIO_ENCODER_CNG_H_
#include <stddef.h>
#include <memory>
#include "api/audio_codecs/audio_encoder.h"
#include "common_audio/vad/include/vad.h"
namespace webrtc {
struct AudioEncoderCngConfig {
// Moveable, not copyable.
AudioEncoderCngConfig();
AudioEncoderCngConfig(AudioEncoderCngConfig&&);
~AudioEncoderCngConfig();
bool IsOk() const;
size_t num_channels = 1;
int payload_type = 13;
std::unique_ptr<AudioEncoder> speech_encoder;
Vad::Aggressiveness vad_mode = Vad::kVadNormal;
int sid_frame_interval_ms = 100;
int num_cng_coefficients = 8;
// The Vad pointer is mainly for testing. If a NULL pointer is passed, the
// AudioEncoderCng creates (and destroys) a Vad object internally. If an
// object is passed, the AudioEncoderCng assumes ownership of the Vad
// object.
Vad* vad = nullptr;
};
std::unique_ptr<AudioEncoder> CreateComfortNoiseEncoder(
AudioEncoderCngConfig&& config);
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_CODECS_CNG_AUDIO_ENCODER_CNG_H_

View File

@ -0,0 +1,520 @@
/*
* Copyright (c) 2014 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/codecs/cng/audio_encoder_cng.h"
#include <memory>
#include <vector>
#include "common_audio/vad/mock/mock_vad.h"
#include "rtc_base/constructor_magic.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "test/gtest.h"
#include "test/mock_audio_encoder.h"
#include "test/testsupport/rtc_expect_death.h"
using ::testing::_;
using ::testing::Eq;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Not;
using ::testing::Optional;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace webrtc {
namespace {
static const size_t kMaxNumSamples = 48 * 10 * 2; // 10 ms @ 48 kHz stereo.
static const size_t kMockReturnEncodedBytes = 17;
static const int kCngPayloadType = 18;
} // namespace
class AudioEncoderCngTest : public ::testing::Test {
protected:
AudioEncoderCngTest()
: mock_encoder_owner_(new MockAudioEncoder),
mock_encoder_(mock_encoder_owner_.get()),
mock_vad_(new MockVad),
timestamp_(4711),
num_audio_samples_10ms_(0),
sample_rate_hz_(8000) {
memset(audio_, 0, kMaxNumSamples * 2);
EXPECT_CALL(*mock_encoder_, NumChannels()).WillRepeatedly(Return(1));
}
void TearDown() override {
EXPECT_CALL(*mock_vad_, Die()).Times(1);
cng_.reset();
}
AudioEncoderCngConfig MakeCngConfig() {
AudioEncoderCngConfig config;
config.speech_encoder = std::move(mock_encoder_owner_);
EXPECT_TRUE(config.speech_encoder);
// Let the AudioEncoderCng object use a MockVad instead of its internally
// created Vad object.
config.vad = mock_vad_;
config.payload_type = kCngPayloadType;
return config;
}
void CreateCng(AudioEncoderCngConfig&& config) {
num_audio_samples_10ms_ = static_cast<size_t>(10 * sample_rate_hz_ / 1000);
ASSERT_LE(num_audio_samples_10ms_, kMaxNumSamples);
if (config.speech_encoder) {
EXPECT_CALL(*mock_encoder_, SampleRateHz())
.WillRepeatedly(Return(sample_rate_hz_));
// Max10MsFramesInAPacket() is just used to verify that the SID frame
// period is not too small. The return value does not matter that much,
// as long as it is smaller than 10.
EXPECT_CALL(*mock_encoder_, Max10MsFramesInAPacket())
.WillOnce(Return(1u));
}
cng_ = CreateComfortNoiseEncoder(std::move(config));
}
void Encode() {
ASSERT_TRUE(cng_) << "Must call CreateCng() first.";
encoded_info_ = cng_->Encode(
timestamp_,
rtc::ArrayView<const int16_t>(audio_, num_audio_samples_10ms_),
&encoded_);
timestamp_ += static_cast<uint32_t>(num_audio_samples_10ms_);
}
// Expect |num_calls| calls to the encoder, all successful. The last call
// claims to have encoded |kMockReturnEncodedBytes| bytes, and all the
// preceding ones 0 bytes.
void ExpectEncodeCalls(size_t num_calls) {
InSequence s;
AudioEncoder::EncodedInfo info;
for (size_t j = 0; j < num_calls - 1; ++j) {
EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _)).WillOnce(Return(info));
}
info.encoded_bytes = kMockReturnEncodedBytes;
EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _))
.WillOnce(
Invoke(MockAudioEncoder::FakeEncoding(kMockReturnEncodedBytes)));
}
// Verifies that the cng_ object waits until it has collected
// |blocks_per_frame| blocks of audio, and then dispatches all of them to
// the underlying codec (speech or cng).
void CheckBlockGrouping(size_t blocks_per_frame, bool active_speech) {
EXPECT_CALL(*mock_encoder_, Num10MsFramesInNextPacket())
.WillRepeatedly(Return(blocks_per_frame));
auto config = MakeCngConfig();
const int num_cng_coefficients = config.num_cng_coefficients;
CreateCng(std::move(config));
EXPECT_CALL(*mock_vad_, VoiceActivity(_, _, _))
.WillRepeatedly(Return(active_speech ? Vad::kActive : Vad::kPassive));
// Don't expect any calls to the encoder yet.
EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _)).Times(0);
for (size_t i = 0; i < blocks_per_frame - 1; ++i) {
Encode();
EXPECT_EQ(0u, encoded_info_.encoded_bytes);
}
if (active_speech)
ExpectEncodeCalls(blocks_per_frame);
Encode();
if (active_speech) {
EXPECT_EQ(kMockReturnEncodedBytes, encoded_info_.encoded_bytes);
} else {
EXPECT_EQ(static_cast<size_t>(num_cng_coefficients + 1),
encoded_info_.encoded_bytes);
}
}
// Verifies that the audio is partitioned into larger blocks before calling
// the VAD.
void CheckVadInputSize(int input_frame_size_ms,
int expected_first_block_size_ms,
int expected_second_block_size_ms) {
const size_t blocks_per_frame =
static_cast<size_t>(input_frame_size_ms / 10);
EXPECT_CALL(*mock_encoder_, Num10MsFramesInNextPacket())
.WillRepeatedly(Return(blocks_per_frame));
// Expect nothing to happen before the last block is sent to cng_.
EXPECT_CALL(*mock_vad_, VoiceActivity(_, _, _)).Times(0);
for (size_t i = 0; i < blocks_per_frame - 1; ++i) {
Encode();
}
// Let the VAD decision be passive, since an active decision may lead to
// early termination of the decision loop.
InSequence s;
EXPECT_CALL(
*mock_vad_,
VoiceActivity(_, expected_first_block_size_ms * sample_rate_hz_ / 1000,
sample_rate_hz_))
.WillOnce(Return(Vad::kPassive));
if (expected_second_block_size_ms > 0) {
EXPECT_CALL(*mock_vad_,
VoiceActivity(
_, expected_second_block_size_ms * sample_rate_hz_ / 1000,
sample_rate_hz_))
.WillOnce(Return(Vad::kPassive));
}
// With this call to Encode(), |mock_vad_| should be called according to the
// above expectations.
Encode();
}
// Tests a frame with both active and passive speech. Returns true if the
// decision was active speech, false if it was passive.
bool CheckMixedActivePassive(Vad::Activity first_type,
Vad::Activity second_type) {
// Set the speech encoder frame size to 60 ms, to ensure that the VAD will
// be called twice.
const size_t blocks_per_frame = 6;
EXPECT_CALL(*mock_encoder_, Num10MsFramesInNextPacket())
.WillRepeatedly(Return(blocks_per_frame));
InSequence s;
EXPECT_CALL(*mock_vad_, VoiceActivity(_, _, _))
.WillOnce(Return(first_type));
if (first_type == Vad::kPassive) {
// Expect a second call to the VAD only if the first frame was passive.
EXPECT_CALL(*mock_vad_, VoiceActivity(_, _, _))
.WillOnce(Return(second_type));
}
encoded_info_.payload_type = 0;
for (size_t i = 0; i < blocks_per_frame; ++i) {
Encode();
}
return encoded_info_.payload_type != kCngPayloadType;
}
std::unique_ptr<AudioEncoder> cng_;
std::unique_ptr<MockAudioEncoder> mock_encoder_owner_;
MockAudioEncoder* mock_encoder_;
MockVad* mock_vad_; // Ownership is transferred to |cng_|.
uint32_t timestamp_;
int16_t audio_[kMaxNumSamples];
size_t num_audio_samples_10ms_;
rtc::Buffer encoded_;
AudioEncoder::EncodedInfo encoded_info_;
int sample_rate_hz_;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioEncoderCngTest);
};
TEST_F(AudioEncoderCngTest, CreateAndDestroy) {
CreateCng(MakeCngConfig());
}
TEST_F(AudioEncoderCngTest, CheckFrameSizePropagation) {
CreateCng(MakeCngConfig());
EXPECT_CALL(*mock_encoder_, Num10MsFramesInNextPacket())
.WillOnce(Return(17U));
EXPECT_EQ(17U, cng_->Num10MsFramesInNextPacket());
}
TEST_F(AudioEncoderCngTest, CheckTargetAudioBitratePropagation) {
CreateCng(MakeCngConfig());
EXPECT_CALL(*mock_encoder_,
OnReceivedUplinkBandwidth(4711, absl::optional<int64_t>()));
cng_->OnReceivedUplinkBandwidth(4711, absl::nullopt);
}
TEST_F(AudioEncoderCngTest, CheckPacketLossFractionPropagation) {
CreateCng(MakeCngConfig());
EXPECT_CALL(*mock_encoder_, OnReceivedUplinkPacketLossFraction(0.5));
cng_->OnReceivedUplinkPacketLossFraction(0.5);
}
TEST_F(AudioEncoderCngTest, CheckGetFrameLengthRangePropagation) {
CreateCng(MakeCngConfig());
auto expected_range =
std::make_pair(TimeDelta::Millis(20), TimeDelta::Millis(20));
EXPECT_CALL(*mock_encoder_, GetFrameLengthRange())
.WillRepeatedly(Return(absl::make_optional(expected_range)));
EXPECT_THAT(cng_->GetFrameLengthRange(), Optional(Eq(expected_range)));
}
TEST_F(AudioEncoderCngTest, EncodeCallsVad) {
EXPECT_CALL(*mock_encoder_, Num10MsFramesInNextPacket())
.WillRepeatedly(Return(1U));
CreateCng(MakeCngConfig());
EXPECT_CALL(*mock_vad_, VoiceActivity(_, _, _))
.WillOnce(Return(Vad::kPassive));
Encode();
}
TEST_F(AudioEncoderCngTest, EncodeCollects1BlockPassiveSpeech) {
CheckBlockGrouping(1, false);
}
TEST_F(AudioEncoderCngTest, EncodeCollects2BlocksPassiveSpeech) {
CheckBlockGrouping(2, false);
}
TEST_F(AudioEncoderCngTest, EncodeCollects3BlocksPassiveSpeech) {
CheckBlockGrouping(3, false);
}
TEST_F(AudioEncoderCngTest, EncodeCollects1BlockActiveSpeech) {
CheckBlockGrouping(1, true);
}
TEST_F(AudioEncoderCngTest, EncodeCollects2BlocksActiveSpeech) {
CheckBlockGrouping(2, true);
}
TEST_F(AudioEncoderCngTest, EncodeCollects3BlocksActiveSpeech) {
CheckBlockGrouping(3, true);
}
TEST_F(AudioEncoderCngTest, EncodePassive) {
const size_t kBlocksPerFrame = 3;
EXPECT_CALL(*mock_encoder_, Num10MsFramesInNextPacket())
.WillRepeatedly(Return(kBlocksPerFrame));
auto config = MakeCngConfig();
const auto sid_frame_interval_ms = config.sid_frame_interval_ms;
const auto num_cng_coefficients = config.num_cng_coefficients;
CreateCng(std::move(config));
EXPECT_CALL(*mock_vad_, VoiceActivity(_, _, _))
.WillRepeatedly(Return(Vad::kPassive));
// Expect no calls at all to the speech encoder mock.
EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _)).Times(0);
uint32_t expected_timestamp = timestamp_;
for (size_t i = 0; i < 100; ++i) {
Encode();
// Check if it was time to call the cng encoder. This is done once every
// |kBlocksPerFrame| calls.
if ((i + 1) % kBlocksPerFrame == 0) {
// Now check if a SID interval has elapsed.
if ((i % (sid_frame_interval_ms / 10)) < kBlocksPerFrame) {
// If so, verify that we got a CNG encoding.
EXPECT_EQ(kCngPayloadType, encoded_info_.payload_type);
EXPECT_FALSE(encoded_info_.speech);
EXPECT_EQ(static_cast<size_t>(num_cng_coefficients) + 1,
encoded_info_.encoded_bytes);
EXPECT_EQ(expected_timestamp, encoded_info_.encoded_timestamp);
}
expected_timestamp += rtc::checked_cast<uint32_t>(
kBlocksPerFrame * num_audio_samples_10ms_);
} else {
// Otherwise, expect no output.
EXPECT_EQ(0u, encoded_info_.encoded_bytes);
}
}
}
// Verifies that the correct action is taken for frames with both active and
// passive speech.
TEST_F(AudioEncoderCngTest, MixedActivePassive) {
CreateCng(MakeCngConfig());
// All of the frame is active speech.
ExpectEncodeCalls(6);
EXPECT_TRUE(CheckMixedActivePassive(Vad::kActive, Vad::kActive));
EXPECT_TRUE(encoded_info_.speech);
// First half of the frame is active speech.
ExpectEncodeCalls(6);
EXPECT_TRUE(CheckMixedActivePassive(Vad::kActive, Vad::kPassive));
EXPECT_TRUE(encoded_info_.speech);
// Second half of the frame is active speech.
ExpectEncodeCalls(6);
EXPECT_TRUE(CheckMixedActivePassive(Vad::kPassive, Vad::kActive));
EXPECT_TRUE(encoded_info_.speech);
// All of the frame is passive speech. Expect no calls to |mock_encoder_|.
EXPECT_FALSE(CheckMixedActivePassive(Vad::kPassive, Vad::kPassive));
EXPECT_FALSE(encoded_info_.speech);
}
// These tests verify that the audio is partitioned into larger blocks before
// calling the VAD.
// The parameters for CheckVadInputSize are:
// CheckVadInputSize(frame_size, expected_first_block_size,
// expected_second_block_size);
TEST_F(AudioEncoderCngTest, VadInputSize10Ms) {
CreateCng(MakeCngConfig());
CheckVadInputSize(10, 10, 0);
}
TEST_F(AudioEncoderCngTest, VadInputSize20Ms) {
CreateCng(MakeCngConfig());
CheckVadInputSize(20, 20, 0);
}
TEST_F(AudioEncoderCngTest, VadInputSize30Ms) {
CreateCng(MakeCngConfig());
CheckVadInputSize(30, 30, 0);
}
TEST_F(AudioEncoderCngTest, VadInputSize40Ms) {
CreateCng(MakeCngConfig());
CheckVadInputSize(40, 20, 20);
}
TEST_F(AudioEncoderCngTest, VadInputSize50Ms) {
CreateCng(MakeCngConfig());
CheckVadInputSize(50, 30, 20);
}
TEST_F(AudioEncoderCngTest, VadInputSize60Ms) {
CreateCng(MakeCngConfig());
CheckVadInputSize(60, 30, 30);
}
// Verifies that the correct payload type is set when CNG is encoded.
TEST_F(AudioEncoderCngTest, VerifyCngPayloadType) {
CreateCng(MakeCngConfig());
EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _)).Times(0);
EXPECT_CALL(*mock_encoder_, Num10MsFramesInNextPacket()).WillOnce(Return(1U));
EXPECT_CALL(*mock_vad_, VoiceActivity(_, _, _))
.WillOnce(Return(Vad::kPassive));
encoded_info_.payload_type = 0;
Encode();
EXPECT_EQ(kCngPayloadType, encoded_info_.payload_type);
}
// Verifies that a SID frame is encoded immediately as the signal changes from
// active speech to passive.
TEST_F(AudioEncoderCngTest, VerifySidFrameAfterSpeech) {
auto config = MakeCngConfig();
const auto num_cng_coefficients = config.num_cng_coefficients;
CreateCng(std::move(config));
EXPECT_CALL(*mock_encoder_, Num10MsFramesInNextPacket())
.WillRepeatedly(Return(1U));
// Start with encoding noise.
EXPECT_CALL(*mock_vad_, VoiceActivity(_, _, _))
.Times(2)
.WillRepeatedly(Return(Vad::kPassive));
Encode();
EXPECT_EQ(kCngPayloadType, encoded_info_.payload_type);
EXPECT_EQ(static_cast<size_t>(num_cng_coefficients) + 1,
encoded_info_.encoded_bytes);
// Encode again, and make sure we got no frame at all (since the SID frame
// period is 100 ms by default).
Encode();
EXPECT_EQ(0u, encoded_info_.encoded_bytes);
// Now encode active speech.
encoded_info_.payload_type = 0;
EXPECT_CALL(*mock_vad_, VoiceActivity(_, _, _))
.WillOnce(Return(Vad::kActive));
EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _))
.WillOnce(
Invoke(MockAudioEncoder::FakeEncoding(kMockReturnEncodedBytes)));
Encode();
EXPECT_EQ(kMockReturnEncodedBytes, encoded_info_.encoded_bytes);
// Go back to noise again, and verify that a SID frame is emitted.
EXPECT_CALL(*mock_vad_, VoiceActivity(_, _, _))
.WillOnce(Return(Vad::kPassive));
Encode();
EXPECT_EQ(kCngPayloadType, encoded_info_.payload_type);
EXPECT_EQ(static_cast<size_t>(num_cng_coefficients) + 1,
encoded_info_.encoded_bytes);
}
// Resetting the CNG should reset both the VAD and the encoder.
TEST_F(AudioEncoderCngTest, Reset) {
CreateCng(MakeCngConfig());
EXPECT_CALL(*mock_encoder_, Reset()).Times(1);
EXPECT_CALL(*mock_vad_, Reset()).Times(1);
cng_->Reset();
}
#if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
// This test fixture tests various error conditions that makes the
// AudioEncoderCng die via CHECKs.
class AudioEncoderCngDeathTest : public AudioEncoderCngTest {
protected:
AudioEncoderCngDeathTest() : AudioEncoderCngTest() {
EXPECT_CALL(*mock_vad_, Die()).Times(1);
delete mock_vad_;
mock_vad_ = nullptr;
}
// Override AudioEncoderCngTest::TearDown, since that one expects a call to
// the destructor of |mock_vad_|. In this case, that object is already
// deleted.
void TearDown() override { cng_.reset(); }
AudioEncoderCngConfig MakeCngConfig() {
// Don't provide a Vad mock object, since it would leak when the test dies.
auto config = AudioEncoderCngTest::MakeCngConfig();
config.vad = nullptr;
return config;
}
void TryWrongNumCoefficients(int num) {
RTC_EXPECT_DEATH(
[&] {
auto config = MakeCngConfig();
config.num_cng_coefficients = num;
CreateCng(std::move(config));
}(),
"Invalid configuration");
}
};
TEST_F(AudioEncoderCngDeathTest, WrongFrameSize) {
CreateCng(MakeCngConfig());
num_audio_samples_10ms_ *= 2; // 20 ms frame.
RTC_EXPECT_DEATH(Encode(), "");
num_audio_samples_10ms_ = 0; // Zero samples.
RTC_EXPECT_DEATH(Encode(), "");
}
TEST_F(AudioEncoderCngDeathTest, WrongNumCoefficientsA) {
TryWrongNumCoefficients(-1);
}
TEST_F(AudioEncoderCngDeathTest, WrongNumCoefficientsB) {
TryWrongNumCoefficients(0);
}
TEST_F(AudioEncoderCngDeathTest, WrongNumCoefficientsC) {
TryWrongNumCoefficients(13);
}
TEST_F(AudioEncoderCngDeathTest, NullSpeechEncoder) {
auto config = MakeCngConfig();
config.speech_encoder = nullptr;
RTC_EXPECT_DEATH(CreateCng(std::move(config)), "");
}
TEST_F(AudioEncoderCngDeathTest, StereoEncoder) {
EXPECT_CALL(*mock_encoder_, NumChannels()).WillRepeatedly(Return(2));
RTC_EXPECT_DEATH(CreateCng(MakeCngConfig()), "Invalid configuration");
}
TEST_F(AudioEncoderCngDeathTest, StereoConfig) {
RTC_EXPECT_DEATH(
[&] {
auto config = MakeCngConfig();
config.num_channels = 2;
CreateCng(std::move(config));
}(),
"Invalid configuration");
}
TEST_F(AudioEncoderCngDeathTest, EncoderFrameSizeTooLarge) {
CreateCng(MakeCngConfig());
EXPECT_CALL(*mock_encoder_, Num10MsFramesInNextPacket())
.WillRepeatedly(Return(7U));
for (int i = 0; i < 6; ++i)
Encode();
RTC_EXPECT_DEATH(
Encode(), "Frame size cannot be larger than 60 ms when using VAD/CNG.");
}
#endif // GTEST_HAS_DEATH_TEST
} // namespace webrtc

View File

@ -0,0 +1,250 @@
/*
* Copyright (c) 2011 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 <memory>
#include <string>
#include "modules/audio_coding/codecs/cng/webrtc_cng.h"
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"
namespace webrtc {
enum {
kSidShortIntervalUpdate = 1,
kSidNormalIntervalUpdate = 100,
kSidLongIntervalUpdate = 10000
};
enum : size_t {
kCNGNumParamsLow = 0,
kCNGNumParamsNormal = 8,
kCNGNumParamsHigh = WEBRTC_CNG_MAX_LPC_ORDER,
kCNGNumParamsTooHigh = WEBRTC_CNG_MAX_LPC_ORDER + 1
};
enum { kNoSid, kForceSid };
class CngTest : public ::testing::Test {
protected:
virtual void SetUp();
void TestCngEncode(int sample_rate_hz, int quality);
int16_t speech_data_[640]; // Max size of CNG internal buffers.
};
void CngTest::SetUp() {
FILE* input_file;
const std::string file_name =
webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm");
input_file = fopen(file_name.c_str(), "rb");
ASSERT_TRUE(input_file != NULL);
ASSERT_EQ(640, static_cast<int32_t>(
fread(speech_data_, sizeof(int16_t), 640, input_file)));
fclose(input_file);
input_file = NULL;
}
void CngTest::TestCngEncode(int sample_rate_hz, int quality) {
const size_t num_samples_10ms = rtc::CheckedDivExact(sample_rate_hz, 100);
rtc::Buffer sid_data;
ComfortNoiseEncoder cng_encoder(sample_rate_hz, kSidNormalIntervalUpdate,
quality);
EXPECT_EQ(0U, cng_encoder.Encode(rtc::ArrayView<const int16_t>(
speech_data_, num_samples_10ms),
kNoSid, &sid_data));
EXPECT_EQ(static_cast<size_t>(quality + 1),
cng_encoder.Encode(
rtc::ArrayView<const int16_t>(speech_data_, num_samples_10ms),
kForceSid, &sid_data));
}
#if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
// Create CNG encoder, init with faulty values, free CNG encoder.
TEST_F(CngTest, CngInitFail) {
// Call with too few parameters.
EXPECT_DEATH(
{
ComfortNoiseEncoder(8000, kSidNormalIntervalUpdate, kCNGNumParamsLow);
},
"");
// Call with too many parameters.
EXPECT_DEATH(
{
ComfortNoiseEncoder(8000, kSidNormalIntervalUpdate,
kCNGNumParamsTooHigh);
},
"");
}
// Encode Cng with too long input vector.
TEST_F(CngTest, CngEncodeTooLong) {
rtc::Buffer sid_data;
// Create encoder.
ComfortNoiseEncoder cng_encoder(8000, kSidNormalIntervalUpdate,
kCNGNumParamsNormal);
// Run encoder with too much data.
EXPECT_DEATH(
cng_encoder.Encode(rtc::ArrayView<const int16_t>(speech_data_, 641),
kNoSid, &sid_data),
"");
}
#endif // GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
TEST_F(CngTest, CngEncode8000) {
TestCngEncode(8000, kCNGNumParamsNormal);
}
TEST_F(CngTest, CngEncode16000) {
TestCngEncode(16000, kCNGNumParamsNormal);
}
TEST_F(CngTest, CngEncode32000) {
TestCngEncode(32000, kCNGNumParamsHigh);
}
TEST_F(CngTest, CngEncode48000) {
TestCngEncode(48000, kCNGNumParamsNormal);
}
TEST_F(CngTest, CngEncode64000) {
TestCngEncode(64000, kCNGNumParamsNormal);
}
// Update SID parameters, for both 9 and 16 parameters.
TEST_F(CngTest, CngUpdateSid) {
rtc::Buffer sid_data;
// Create and initialize encoder and decoder.
ComfortNoiseEncoder cng_encoder(16000, kSidNormalIntervalUpdate,
kCNGNumParamsNormal);
ComfortNoiseDecoder cng_decoder;
// Run normal Encode and UpdateSid.
EXPECT_EQ(kCNGNumParamsNormal + 1,
cng_encoder.Encode(rtc::ArrayView<const int16_t>(speech_data_, 160),
kForceSid, &sid_data));
cng_decoder.UpdateSid(sid_data);
// Reinit with new length.
cng_encoder.Reset(16000, kSidNormalIntervalUpdate, kCNGNumParamsHigh);
cng_decoder.Reset();
// Expect 0 because of unstable parameters after switching length.
EXPECT_EQ(0U,
cng_encoder.Encode(rtc::ArrayView<const int16_t>(speech_data_, 160),
kForceSid, &sid_data));
EXPECT_EQ(
kCNGNumParamsHigh + 1,
cng_encoder.Encode(rtc::ArrayView<const int16_t>(speech_data_ + 160, 160),
kForceSid, &sid_data));
cng_decoder.UpdateSid(
rtc::ArrayView<const uint8_t>(sid_data.data(), kCNGNumParamsNormal + 1));
}
// Update SID parameters, with wrong parameters or without calling decode.
TEST_F(CngTest, CngUpdateSidErroneous) {
rtc::Buffer sid_data;
// Encode.
ComfortNoiseEncoder cng_encoder(16000, kSidNormalIntervalUpdate,
kCNGNumParamsNormal);
ComfortNoiseDecoder cng_decoder;
EXPECT_EQ(kCNGNumParamsNormal + 1,
cng_encoder.Encode(rtc::ArrayView<const int16_t>(speech_data_, 160),
kForceSid, &sid_data));
// First run with valid parameters, then with too many CNG parameters.
// The function will operate correctly by only reading the maximum number of
// parameters, skipping the extra.
EXPECT_EQ(kCNGNumParamsNormal + 1, sid_data.size());
cng_decoder.UpdateSid(sid_data);
// Make sure the input buffer is large enough. Since Encode() appends data, we
// need to set the size manually only afterwards, or the buffer will be bigger
// than anticipated.
sid_data.SetSize(kCNGNumParamsTooHigh + 1);
cng_decoder.UpdateSid(sid_data);
}
// Test to generate cng data, by forcing SID. Both normal and faulty condition.
TEST_F(CngTest, CngGenerate) {
rtc::Buffer sid_data;
int16_t out_data[640];
// Create and initialize encoder and decoder.
ComfortNoiseEncoder cng_encoder(16000, kSidNormalIntervalUpdate,
kCNGNumParamsNormal);
ComfortNoiseDecoder cng_decoder;
// Normal Encode.
EXPECT_EQ(kCNGNumParamsNormal + 1,
cng_encoder.Encode(rtc::ArrayView<const int16_t>(speech_data_, 160),
kForceSid, &sid_data));
// Normal UpdateSid.
cng_decoder.UpdateSid(sid_data);
// Two normal Generate, one with new_period.
EXPECT_TRUE(cng_decoder.Generate(rtc::ArrayView<int16_t>(out_data, 640), 1));
EXPECT_TRUE(cng_decoder.Generate(rtc::ArrayView<int16_t>(out_data, 640), 0));
// Call Genereate with too much data.
EXPECT_FALSE(cng_decoder.Generate(rtc::ArrayView<int16_t>(out_data, 641), 0));
}
// Test automatic SID.
TEST_F(CngTest, CngAutoSid) {
rtc::Buffer sid_data;
// Create and initialize encoder and decoder.
ComfortNoiseEncoder cng_encoder(16000, kSidNormalIntervalUpdate,
kCNGNumParamsNormal);
ComfortNoiseDecoder cng_decoder;
// Normal Encode, 100 msec, where no SID data should be generated.
for (int i = 0; i < 10; i++) {
EXPECT_EQ(
0U, cng_encoder.Encode(rtc::ArrayView<const int16_t>(speech_data_, 160),
kNoSid, &sid_data));
}
// We have reached 100 msec, and SID data should be generated.
EXPECT_EQ(kCNGNumParamsNormal + 1,
cng_encoder.Encode(rtc::ArrayView<const int16_t>(speech_data_, 160),
kNoSid, &sid_data));
}
// Test automatic SID, with very short interval.
TEST_F(CngTest, CngAutoSidShort) {
rtc::Buffer sid_data;
// Create and initialize encoder and decoder.
ComfortNoiseEncoder cng_encoder(16000, kSidShortIntervalUpdate,
kCNGNumParamsNormal);
ComfortNoiseDecoder cng_decoder;
// First call will never generate SID, unless forced to.
EXPECT_EQ(0U,
cng_encoder.Encode(rtc::ArrayView<const int16_t>(speech_data_, 160),
kNoSid, &sid_data));
// Normal Encode, 100 msec, SID data should be generated all the time.
for (int i = 0; i < 10; i++) {
EXPECT_EQ(
kCNGNumParamsNormal + 1,
cng_encoder.Encode(rtc::ArrayView<const int16_t>(speech_data_, 160),
kNoSid, &sid_data));
}
}
} // namespace webrtc

View File

@ -0,0 +1,436 @@
/*
* 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/codecs/cng/webrtc_cng.h"
#include <algorithm>
#include "common_audio/signal_processing/include/signal_processing_library.h"
#include "rtc_base/checks.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
namespace {
const size_t kCngMaxOutsizeOrder = 640;
// TODO(ossu): Rename the left-over WebRtcCng according to style guide.
void WebRtcCng_K2a16(int16_t* k, int useOrder, int16_t* a);
const int32_t WebRtcCng_kDbov[94] = {
1081109975, 858756178, 682134279, 541838517, 430397633, 341876992,
271562548, 215709799, 171344384, 136103682, 108110997, 85875618,
68213428, 54183852, 43039763, 34187699, 27156255, 21570980,
17134438, 13610368, 10811100, 8587562, 6821343, 5418385,
4303976, 3418770, 2715625, 2157098, 1713444, 1361037,
1081110, 858756, 682134, 541839, 430398, 341877,
271563, 215710, 171344, 136104, 108111, 85876,
68213, 54184, 43040, 34188, 27156, 21571,
17134, 13610, 10811, 8588, 6821, 5418,
4304, 3419, 2716, 2157, 1713, 1361,
1081, 859, 682, 542, 430, 342,
272, 216, 171, 136, 108, 86,
68, 54, 43, 34, 27, 22,
17, 14, 11, 9, 7, 5,
4, 3, 3, 2, 2, 1,
1, 1, 1, 1};
const int16_t WebRtcCng_kCorrWindow[WEBRTC_CNG_MAX_LPC_ORDER] = {
32702, 32636, 32570, 32505, 32439, 32374,
32309, 32244, 32179, 32114, 32049, 31985};
} // namespace
ComfortNoiseDecoder::ComfortNoiseDecoder() {
/* Needed to get the right function pointers in SPLIB. */
Reset();
}
void ComfortNoiseDecoder::Reset() {
dec_seed_ = 7777; /* For debugging only. */
dec_target_energy_ = 0;
dec_used_energy_ = 0;
for (auto& c : dec_target_reflCoefs_)
c = 0;
for (auto& c : dec_used_reflCoefs_)
c = 0;
for (auto& c : dec_filtstate_)
c = 0;
for (auto& c : dec_filtstateLow_)
c = 0;
dec_order_ = 5;
dec_target_scale_factor_ = 0;
dec_used_scale_factor_ = 0;
}
void ComfortNoiseDecoder::UpdateSid(rtc::ArrayView<const uint8_t> sid) {
int16_t refCs[WEBRTC_CNG_MAX_LPC_ORDER];
int32_t targetEnergy;
size_t length = sid.size();
/* Throw away reflection coefficients of higher order than we can handle. */
if (length > (WEBRTC_CNG_MAX_LPC_ORDER + 1))
length = WEBRTC_CNG_MAX_LPC_ORDER + 1;
dec_order_ = static_cast<uint16_t>(length - 1);
uint8_t sid0 = std::min<uint8_t>(sid[0], 93);
targetEnergy = WebRtcCng_kDbov[sid0];
/* Take down target energy to 75%. */
targetEnergy = targetEnergy >> 1;
targetEnergy += targetEnergy >> 2;
dec_target_energy_ = targetEnergy;
/* Reconstruct coeffs with tweak for WebRtc implementation of RFC3389. */
if (dec_order_ == WEBRTC_CNG_MAX_LPC_ORDER) {
for (size_t i = 0; i < (dec_order_); i++) {
refCs[i] = sid[i + 1] << 8; /* Q7 to Q15*/
dec_target_reflCoefs_[i] = refCs[i];
}
} else {
for (size_t i = 0; i < (dec_order_); i++) {
refCs[i] = (sid[i + 1] - 127) * (1 << 8); /* Q7 to Q15. */
dec_target_reflCoefs_[i] = refCs[i];
}
}
for (size_t i = (dec_order_); i < WEBRTC_CNG_MAX_LPC_ORDER; i++) {
refCs[i] = 0;
dec_target_reflCoefs_[i] = refCs[i];
}
}
bool ComfortNoiseDecoder::Generate(rtc::ArrayView<int16_t> out_data,
bool new_period) {
int16_t excitation[kCngMaxOutsizeOrder];
int16_t low[kCngMaxOutsizeOrder];
int16_t lpPoly[WEBRTC_CNG_MAX_LPC_ORDER + 1];
int16_t ReflBetaStd = 26214; /* 0.8 in q15. */
int16_t ReflBetaCompStd = 6553; /* 0.2 in q15. */
int16_t ReflBetaNewP = 19661; /* 0.6 in q15. */
int16_t ReflBetaCompNewP = 13107; /* 0.4 in q15. */
int16_t Beta, BetaC; /* These are in Q15. */
int32_t targetEnergy;
int16_t En;
int16_t temp16;
const size_t num_samples = out_data.size();
if (num_samples > kCngMaxOutsizeOrder) {
return false;
}
if (new_period) {
dec_used_scale_factor_ = dec_target_scale_factor_;
Beta = ReflBetaNewP;
BetaC = ReflBetaCompNewP;
} else {
Beta = ReflBetaStd;
BetaC = ReflBetaCompStd;
}
/* Calculate new scale factor in Q13 */
dec_used_scale_factor_ = rtc::checked_cast<int16_t>(
WEBRTC_SPL_MUL_16_16_RSFT(dec_used_scale_factor_, Beta >> 2, 13) +
WEBRTC_SPL_MUL_16_16_RSFT(dec_target_scale_factor_, BetaC >> 2, 13));
dec_used_energy_ = dec_used_energy_ >> 1;
dec_used_energy_ += dec_target_energy_ >> 1;
/* Do the same for the reflection coeffs, albeit in Q15. */
for (size_t i = 0; i < WEBRTC_CNG_MAX_LPC_ORDER; i++) {
dec_used_reflCoefs_[i] =
(int16_t)WEBRTC_SPL_MUL_16_16_RSFT(dec_used_reflCoefs_[i], Beta, 15);
dec_used_reflCoefs_[i] +=
(int16_t)WEBRTC_SPL_MUL_16_16_RSFT(dec_target_reflCoefs_[i], BetaC, 15);
}
/* Compute the polynomial coefficients. */
WebRtcCng_K2a16(dec_used_reflCoefs_, WEBRTC_CNG_MAX_LPC_ORDER, lpPoly);
targetEnergy = dec_used_energy_;
/* Calculate scaling factor based on filter energy. */
En = 8192; /* 1.0 in Q13. */
for (size_t i = 0; i < (WEBRTC_CNG_MAX_LPC_ORDER); i++) {
/* Floating point value for reference.
E *= 1.0 - (dec_used_reflCoefs_[i] / 32768.0) *
(dec_used_reflCoefs_[i] / 32768.0);
*/
/* Same in fixed point. */
/* K(i).^2 in Q15. */
temp16 = (int16_t)WEBRTC_SPL_MUL_16_16_RSFT(dec_used_reflCoefs_[i],
dec_used_reflCoefs_[i], 15);
/* 1 - K(i).^2 in Q15. */
temp16 = 0x7fff - temp16;
En = (int16_t)WEBRTC_SPL_MUL_16_16_RSFT(En, temp16, 15);
}
/* float scaling= sqrt(E * dec_target_energy_ / (1 << 24)); */
/* Calculate sqrt(En * target_energy / excitation energy) */
targetEnergy = WebRtcSpl_Sqrt(dec_used_energy_);
En = (int16_t)WebRtcSpl_Sqrt(En) << 6;
En = (En * 3) >> 1; /* 1.5 estimates sqrt(2). */
dec_used_scale_factor_ = (int16_t)((En * targetEnergy) >> 12);
/* Generate excitation. */
/* Excitation energy per sample is 2.^24 - Q13 N(0,1). */
for (size_t i = 0; i < num_samples; i++) {
excitation[i] = WebRtcSpl_RandN(&dec_seed_) >> 1;
}
/* Scale to correct energy. */
WebRtcSpl_ScaleVector(excitation, excitation, dec_used_scale_factor_,
num_samples, 13);
/* |lpPoly| - Coefficients in Q12.
* |excitation| - Speech samples.
* |nst->dec_filtstate| - State preservation.
* |out_data| - Filtered speech samples. */
WebRtcSpl_FilterAR(lpPoly, WEBRTC_CNG_MAX_LPC_ORDER + 1, excitation,
num_samples, dec_filtstate_, WEBRTC_CNG_MAX_LPC_ORDER,
dec_filtstateLow_, WEBRTC_CNG_MAX_LPC_ORDER,
out_data.data(), low, num_samples);
return true;
}
ComfortNoiseEncoder::ComfortNoiseEncoder(int fs, int interval, int quality)
: enc_nrOfCoefs_(quality),
enc_sampfreq_(fs),
enc_interval_(interval),
enc_msSinceSid_(0),
enc_Energy_(0),
enc_reflCoefs_{0},
enc_corrVector_{0},
enc_seed_(7777) /* For debugging only. */ {
RTC_CHECK_GT(quality, 0);
RTC_CHECK_LE(quality, WEBRTC_CNG_MAX_LPC_ORDER);
}
void ComfortNoiseEncoder::Reset(int fs, int interval, int quality) {
RTC_CHECK_GT(quality, 0);
RTC_CHECK_LE(quality, WEBRTC_CNG_MAX_LPC_ORDER);
enc_nrOfCoefs_ = quality;
enc_sampfreq_ = fs;
enc_interval_ = interval;
enc_msSinceSid_ = 0;
enc_Energy_ = 0;
for (auto& c : enc_reflCoefs_)
c = 0;
for (auto& c : enc_corrVector_)
c = 0;
enc_seed_ = 7777; /* For debugging only. */
}
size_t ComfortNoiseEncoder::Encode(rtc::ArrayView<const int16_t> speech,
bool force_sid,
rtc::Buffer* output) {
int16_t arCoefs[WEBRTC_CNG_MAX_LPC_ORDER + 1];
int32_t corrVector[WEBRTC_CNG_MAX_LPC_ORDER + 1];
int16_t refCs[WEBRTC_CNG_MAX_LPC_ORDER + 1];
int16_t hanningW[kCngMaxOutsizeOrder];
int16_t ReflBeta = 19661; /* 0.6 in q15. */
int16_t ReflBetaComp = 13107; /* 0.4 in q15. */
int32_t outEnergy;
int outShifts;
size_t i;
int stab;
int acorrScale;
size_t index;
size_t ind, factor;
int32_t* bptr;
int32_t blo, bhi;
int16_t negate;
const int16_t* aptr;
int16_t speechBuf[kCngMaxOutsizeOrder];
const size_t num_samples = speech.size();
RTC_CHECK_LE(num_samples, kCngMaxOutsizeOrder);
for (i = 0; i < num_samples; i++) {
speechBuf[i] = speech[i];
}
factor = num_samples;
/* Calculate energy and a coefficients. */
outEnergy = WebRtcSpl_Energy(speechBuf, num_samples, &outShifts);
while (outShifts > 0) {
/* We can only do 5 shifts without destroying accuracy in
* division factor. */
if (outShifts > 5) {
outEnergy <<= (outShifts - 5);
outShifts = 5;
} else {
factor /= 2;
outShifts--;
}
}
outEnergy = WebRtcSpl_DivW32W16(outEnergy, (int16_t)factor);
if (outEnergy > 1) {
/* Create Hanning Window. */
WebRtcSpl_GetHanningWindow(hanningW, num_samples / 2);
for (i = 0; i < (num_samples / 2); i++)
hanningW[num_samples - i - 1] = hanningW[i];
WebRtcSpl_ElementwiseVectorMult(speechBuf, hanningW, speechBuf, num_samples,
14);
WebRtcSpl_AutoCorrelation(speechBuf, num_samples, enc_nrOfCoefs_,
corrVector, &acorrScale);
if (*corrVector == 0)
*corrVector = WEBRTC_SPL_WORD16_MAX;
/* Adds the bandwidth expansion. */
aptr = WebRtcCng_kCorrWindow;
bptr = corrVector;
/* (zzz) lpc16_1 = 17+1+820+2+2 = 842 (ordo2=700). */
for (ind = 0; ind < enc_nrOfCoefs_; ind++) {
/* The below code multiplies the 16 b corrWindow values (Q15) with
* the 32 b corrvector (Q0) and shifts the result down 15 steps. */
negate = *bptr < 0;
if (negate)
*bptr = -*bptr;
blo = (int32_t)*aptr * (*bptr & 0xffff);
bhi = ((blo >> 16) & 0xffff) +
((int32_t)(*aptr++) * ((*bptr >> 16) & 0xffff));
blo = (blo & 0xffff) | ((bhi & 0xffff) << 16);
*bptr = (((bhi >> 16) & 0x7fff) << 17) | ((uint32_t)blo >> 15);
if (negate)
*bptr = -*bptr;
bptr++;
}
/* End of bandwidth expansion. */
stab = WebRtcSpl_LevinsonDurbin(corrVector, arCoefs, refCs, enc_nrOfCoefs_);
if (!stab) {
/* Disregard from this frame */
return 0;
}
} else {
for (i = 0; i < enc_nrOfCoefs_; i++)
refCs[i] = 0;
}
if (force_sid) {
/* Read instantaneous values instead of averaged. */
for (i = 0; i < enc_nrOfCoefs_; i++)
enc_reflCoefs_[i] = refCs[i];
enc_Energy_ = outEnergy;
} else {
/* Average history with new values. */
for (i = 0; i < enc_nrOfCoefs_; i++) {
enc_reflCoefs_[i] =
(int16_t)WEBRTC_SPL_MUL_16_16_RSFT(enc_reflCoefs_[i], ReflBeta, 15);
enc_reflCoefs_[i] +=
(int16_t)WEBRTC_SPL_MUL_16_16_RSFT(refCs[i], ReflBetaComp, 15);
}
enc_Energy_ = (outEnergy >> 2) + (enc_Energy_ >> 1) + (enc_Energy_ >> 2);
}
if (enc_Energy_ < 1) {
enc_Energy_ = 1;
}
if ((enc_msSinceSid_ > (enc_interval_ - 1)) || force_sid) {
/* Search for best dbov value. */
index = 0;
for (i = 1; i < 93; i++) {
/* Always round downwards. */
if ((enc_Energy_ - WebRtcCng_kDbov[i]) > 0) {
index = i;
break;
}
}
if ((i == 93) && (index == 0))
index = 94;
const size_t output_coefs = enc_nrOfCoefs_ + 1;
output->AppendData(output_coefs, [&](rtc::ArrayView<uint8_t> output) {
output[0] = (uint8_t)index;
/* Quantize coefficients with tweak for WebRtc implementation of
* RFC3389. */
if (enc_nrOfCoefs_ == WEBRTC_CNG_MAX_LPC_ORDER) {
for (i = 0; i < enc_nrOfCoefs_; i++) {
/* Q15 to Q7 with rounding. */
output[i + 1] = ((enc_reflCoefs_[i] + 128) >> 8);
}
} else {
for (i = 0; i < enc_nrOfCoefs_; i++) {
/* Q15 to Q7 with rounding. */
output[i + 1] = (127 + ((enc_reflCoefs_[i] + 128) >> 8));
}
}
return output_coefs;
});
enc_msSinceSid_ =
static_cast<int16_t>((1000 * num_samples) / enc_sampfreq_);
return output_coefs;
} else {
enc_msSinceSid_ +=
static_cast<int16_t>((1000 * num_samples) / enc_sampfreq_);
return 0;
}
}
namespace {
/* Values in |k| are Q15, and |a| Q12. */
void WebRtcCng_K2a16(int16_t* k, int useOrder, int16_t* a) {
int16_t any[WEBRTC_SPL_MAX_LPC_ORDER + 1];
int16_t* aptr;
int16_t* aptr2;
int16_t* anyptr;
const int16_t* kptr;
int m, i;
kptr = k;
*a = 4096; /* i.e., (Word16_MAX >> 3) + 1 */
*any = *a;
a[1] = (*k + 4) >> 3;
for (m = 1; m < useOrder; m++) {
kptr++;
aptr = a;
aptr++;
aptr2 = &a[m];
anyptr = any;
anyptr++;
any[m + 1] = (*kptr + 4) >> 3;
for (i = 0; i < m; i++) {
*anyptr++ =
(*aptr++) +
(int16_t)((((int32_t)(*aptr2--) * (int32_t)*kptr) + 16384) >> 15);
}
aptr = a;
anyptr = any;
for (i = 0; i < (m + 2); i++) {
*aptr++ = *anyptr++;
}
}
}
} // namespace
} // namespace webrtc

View File

@ -0,0 +1,99 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_CODING_CODECS_CNG_WEBRTC_CNG_H_
#define MODULES_AUDIO_CODING_CODECS_CNG_WEBRTC_CNG_H_
#include <stdint.h>
#include <cstddef>
#include "api/array_view.h"
#include "rtc_base/buffer.h"
#define WEBRTC_CNG_MAX_LPC_ORDER 12
namespace webrtc {
class ComfortNoiseDecoder {
public:
ComfortNoiseDecoder();
~ComfortNoiseDecoder() = default;
ComfortNoiseDecoder(const ComfortNoiseDecoder&) = delete;
ComfortNoiseDecoder& operator=(const ComfortNoiseDecoder&) = delete;
void Reset();
// Updates the CN state when a new SID packet arrives.
// |sid| is a view of the SID packet without the headers.
void UpdateSid(rtc::ArrayView<const uint8_t> sid);
// Generates comfort noise.
// |out_data| will be filled with samples - its size determines the number of
// samples generated. When |new_period| is true, CNG history will be reset
// before any audio is generated. Returns |false| if outData is too large -
// currently 640 bytes (equalling 10ms at 64kHz).
// TODO(ossu): Specify better limits for the size of out_data. Either let it
// be unbounded or limit to 10ms in the current sample rate.
bool Generate(rtc::ArrayView<int16_t> out_data, bool new_period);
private:
uint32_t dec_seed_;
int32_t dec_target_energy_;
int32_t dec_used_energy_;
int16_t dec_target_reflCoefs_[WEBRTC_CNG_MAX_LPC_ORDER + 1];
int16_t dec_used_reflCoefs_[WEBRTC_CNG_MAX_LPC_ORDER + 1];
int16_t dec_filtstate_[WEBRTC_CNG_MAX_LPC_ORDER + 1];
int16_t dec_filtstateLow_[WEBRTC_CNG_MAX_LPC_ORDER + 1];
uint16_t dec_order_;
int16_t dec_target_scale_factor_; /* Q29 */
int16_t dec_used_scale_factor_; /* Q29 */
};
class ComfortNoiseEncoder {
public:
// Creates a comfort noise encoder.
// |fs| selects sample rate: 8000 for narrowband or 16000 for wideband.
// |interval| sets the interval at which to generate SID data (in ms).
// |quality| selects the number of refl. coeffs. Maximum allowed is 12.
ComfortNoiseEncoder(int fs, int interval, int quality);
~ComfortNoiseEncoder() = default;
ComfortNoiseEncoder(const ComfortNoiseEncoder&) = delete;
ComfortNoiseEncoder& operator=(const ComfortNoiseEncoder&) = delete;
// Resets the comfort noise encoder to its initial state.
// Parameters are set as during construction.
void Reset(int fs, int interval, int quality);
// Analyzes background noise from |speech| and appends coefficients to
// |output|. Returns the number of coefficients generated. If |force_sid| is
// true, a SID frame is forced and the internal sid interval counter is reset.
// Will fail if the input size is too large (> 640 samples, see
// ComfortNoiseDecoder::Generate).
size_t Encode(rtc::ArrayView<const int16_t> speech,
bool force_sid,
rtc::Buffer* output);
private:
size_t enc_nrOfCoefs_;
int enc_sampfreq_;
int16_t enc_interval_;
int16_t enc_msSinceSid_;
int32_t enc_Energy_;
int16_t enc_reflCoefs_[WEBRTC_CNG_MAX_LPC_ORDER + 1];
int32_t enc_corrVector_[WEBRTC_CNG_MAX_LPC_ORDER + 1];
uint32_t enc_seed_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_CODECS_CNG_WEBRTC_CNG_H_

View File

@ -0,0 +1,90 @@
/*
* Copyright (c) 2015 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/codecs/g711/audio_decoder_pcm.h"
#include <utility>
#include "modules/audio_coding/codecs/g711/g711_interface.h"
#include "modules/audio_coding/codecs/legacy_encoded_audio_frame.h"
namespace webrtc {
void AudioDecoderPcmU::Reset() {}
std::vector<AudioDecoder::ParseResult> AudioDecoderPcmU::ParsePayload(
rtc::Buffer&& payload,
uint32_t timestamp) {
return LegacyEncodedAudioFrame::SplitBySamples(
this, std::move(payload), timestamp, 8 * num_channels_, 8);
}
int AudioDecoderPcmU::SampleRateHz() const {
return 8000;
}
size_t AudioDecoderPcmU::Channels() const {
return num_channels_;
}
int AudioDecoderPcmU::DecodeInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) {
RTC_DCHECK_EQ(SampleRateHz(), sample_rate_hz);
int16_t temp_type = 1; // Default is speech.
size_t ret = WebRtcG711_DecodeU(encoded, encoded_len, decoded, &temp_type);
*speech_type = ConvertSpeechType(temp_type);
return static_cast<int>(ret);
}
int AudioDecoderPcmU::PacketDuration(const uint8_t* encoded,
size_t encoded_len) const {
// One encoded byte per sample per channel.
return static_cast<int>(encoded_len / Channels());
}
void AudioDecoderPcmA::Reset() {}
std::vector<AudioDecoder::ParseResult> AudioDecoderPcmA::ParsePayload(
rtc::Buffer&& payload,
uint32_t timestamp) {
return LegacyEncodedAudioFrame::SplitBySamples(
this, std::move(payload), timestamp, 8 * num_channels_, 8);
}
int AudioDecoderPcmA::SampleRateHz() const {
return 8000;
}
size_t AudioDecoderPcmA::Channels() const {
return num_channels_;
}
int AudioDecoderPcmA::DecodeInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) {
RTC_DCHECK_EQ(SampleRateHz(), sample_rate_hz);
int16_t temp_type = 1; // Default is speech.
size_t ret = WebRtcG711_DecodeA(encoded, encoded_len, decoded, &temp_type);
*speech_type = ConvertSpeechType(temp_type);
return static_cast<int>(ret);
}
int AudioDecoderPcmA::PacketDuration(const uint8_t* encoded,
size_t encoded_len) const {
// One encoded byte per sample per channel.
return static_cast<int>(encoded_len / Channels());
}
} // namespace webrtc

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2015 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.
*/
#ifndef MODULES_AUDIO_CODING_CODECS_G711_AUDIO_DECODER_PCM_H_
#define MODULES_AUDIO_CODING_CODECS_G711_AUDIO_DECODER_PCM_H_
#include <stddef.h>
#include <stdint.h>
#include <vector>
#include "api/audio_codecs/audio_decoder.h"
#include "rtc_base/buffer.h"
#include "rtc_base/checks.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
class AudioDecoderPcmU final : public AudioDecoder {
public:
explicit AudioDecoderPcmU(size_t num_channels) : num_channels_(num_channels) {
RTC_DCHECK_GE(num_channels, 1);
}
void Reset() override;
std::vector<ParseResult> ParsePayload(rtc::Buffer&& payload,
uint32_t timestamp) override;
int PacketDuration(const uint8_t* encoded, size_t encoded_len) const override;
int SampleRateHz() const override;
size_t Channels() const override;
protected:
int DecodeInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) override;
private:
const size_t num_channels_;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioDecoderPcmU);
};
class AudioDecoderPcmA final : public AudioDecoder {
public:
explicit AudioDecoderPcmA(size_t num_channels) : num_channels_(num_channels) {
RTC_DCHECK_GE(num_channels, 1);
}
void Reset() override;
std::vector<ParseResult> ParsePayload(rtc::Buffer&& payload,
uint32_t timestamp) override;
int PacketDuration(const uint8_t* encoded, size_t encoded_len) const override;
int SampleRateHz() const override;
size_t Channels() const override;
protected:
int DecodeInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) override;
private:
const size_t num_channels_;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioDecoderPcmA);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_CODECS_G711_AUDIO_DECODER_PCM_H_

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2014 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/codecs/g711/audio_encoder_pcm.h"
#include <cstdint>
#include "modules/audio_coding/codecs/g711/g711_interface.h"
#include "rtc_base/checks.h"
namespace webrtc {
bool AudioEncoderPcm::Config::IsOk() const {
return (frame_size_ms % 10 == 0) && (num_channels >= 1);
}
AudioEncoderPcm::AudioEncoderPcm(const Config& config, int sample_rate_hz)
: sample_rate_hz_(sample_rate_hz),
num_channels_(config.num_channels),
payload_type_(config.payload_type),
num_10ms_frames_per_packet_(
static_cast<size_t>(config.frame_size_ms / 10)),
full_frame_samples_(config.num_channels * config.frame_size_ms *
sample_rate_hz / 1000),
first_timestamp_in_buffer_(0) {
RTC_CHECK_GT(sample_rate_hz, 0) << "Sample rate must be larger than 0 Hz";
RTC_CHECK_EQ(config.frame_size_ms % 10, 0)
<< "Frame size must be an integer multiple of 10 ms.";
speech_buffer_.reserve(full_frame_samples_);
}
AudioEncoderPcm::~AudioEncoderPcm() = default;
int AudioEncoderPcm::SampleRateHz() const {
return sample_rate_hz_;
}
size_t AudioEncoderPcm::NumChannels() const {
return num_channels_;
}
size_t AudioEncoderPcm::Num10MsFramesInNextPacket() const {
return num_10ms_frames_per_packet_;
}
size_t AudioEncoderPcm::Max10MsFramesInAPacket() const {
return num_10ms_frames_per_packet_;
}
int AudioEncoderPcm::GetTargetBitrate() const {
return static_cast<int>(8 * BytesPerSample() * SampleRateHz() *
NumChannels());
}
AudioEncoder::EncodedInfo AudioEncoderPcm::EncodeImpl(
uint32_t rtp_timestamp,
rtc::ArrayView<const int16_t> audio,
rtc::Buffer* encoded) {
if (speech_buffer_.empty()) {
first_timestamp_in_buffer_ = rtp_timestamp;
}
speech_buffer_.insert(speech_buffer_.end(), audio.begin(), audio.end());
if (speech_buffer_.size() < full_frame_samples_) {
return EncodedInfo();
}
RTC_CHECK_EQ(speech_buffer_.size(), full_frame_samples_);
EncodedInfo info;
info.encoded_timestamp = first_timestamp_in_buffer_;
info.payload_type = payload_type_;
info.encoded_bytes = encoded->AppendData(
full_frame_samples_ * BytesPerSample(),
[&](rtc::ArrayView<uint8_t> encoded) {
return EncodeCall(&speech_buffer_[0], full_frame_samples_,
encoded.data());
});
speech_buffer_.clear();
info.encoder_type = GetCodecType();
return info;
}
void AudioEncoderPcm::Reset() {
speech_buffer_.clear();
}
absl::optional<std::pair<TimeDelta, TimeDelta>>
AudioEncoderPcm::GetFrameLengthRange() const {
return {{TimeDelta::Millis(num_10ms_frames_per_packet_ * 10),
TimeDelta::Millis(num_10ms_frames_per_packet_ * 10)}};
}
size_t AudioEncoderPcmA::EncodeCall(const int16_t* audio,
size_t input_len,
uint8_t* encoded) {
return WebRtcG711_EncodeA(audio, input_len, encoded);
}
size_t AudioEncoderPcmA::BytesPerSample() const {
return 1;
}
AudioEncoder::CodecType AudioEncoderPcmA::GetCodecType() const {
return AudioEncoder::CodecType::kPcmA;
}
size_t AudioEncoderPcmU::EncodeCall(const int16_t* audio,
size_t input_len,
uint8_t* encoded) {
return WebRtcG711_EncodeU(audio, input_len, encoded);
}
size_t AudioEncoderPcmU::BytesPerSample() const {
return 1;
}
AudioEncoder::CodecType AudioEncoderPcmU::GetCodecType() const {
return AudioEncoder::CodecType::kPcmU;
}
} // namespace webrtc

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) 2014 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.
*/
#ifndef MODULES_AUDIO_CODING_CODECS_G711_AUDIO_ENCODER_PCM_H_
#define MODULES_AUDIO_CODING_CODECS_G711_AUDIO_ENCODER_PCM_H_
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/audio_codecs/audio_encoder.h"
#include "api/units/time_delta.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
class AudioEncoderPcm : public AudioEncoder {
public:
struct Config {
public:
bool IsOk() const;
int frame_size_ms;
size_t num_channels;
int payload_type;
protected:
explicit Config(int pt)
: frame_size_ms(20), num_channels(1), payload_type(pt) {}
};
~AudioEncoderPcm() override;
int SampleRateHz() const override;
size_t NumChannels() const override;
size_t Num10MsFramesInNextPacket() const override;
size_t Max10MsFramesInAPacket() const override;
int GetTargetBitrate() const override;
void Reset() override;
absl::optional<std::pair<TimeDelta, TimeDelta>> GetFrameLengthRange()
const override;
protected:
AudioEncoderPcm(const Config& config, int sample_rate_hz);
EncodedInfo EncodeImpl(uint32_t rtp_timestamp,
rtc::ArrayView<const int16_t> audio,
rtc::Buffer* encoded) override;
virtual size_t EncodeCall(const int16_t* audio,
size_t input_len,
uint8_t* encoded) = 0;
virtual size_t BytesPerSample() const = 0;
// Used to set EncodedInfoLeaf::encoder_type in
// AudioEncoderPcm::EncodeImpl
virtual AudioEncoder::CodecType GetCodecType() const = 0;
private:
const int sample_rate_hz_;
const size_t num_channels_;
const int payload_type_;
const size_t num_10ms_frames_per_packet_;
const size_t full_frame_samples_;
std::vector<int16_t> speech_buffer_;
uint32_t first_timestamp_in_buffer_;
};
class AudioEncoderPcmA final : public AudioEncoderPcm {
public:
struct Config : public AudioEncoderPcm::Config {
Config() : AudioEncoderPcm::Config(8) {}
};
explicit AudioEncoderPcmA(const Config& config)
: AudioEncoderPcm(config, kSampleRateHz) {}
protected:
size_t EncodeCall(const int16_t* audio,
size_t input_len,
uint8_t* encoded) override;
size_t BytesPerSample() const override;
AudioEncoder::CodecType GetCodecType() const override;
private:
static const int kSampleRateHz = 8000;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioEncoderPcmA);
};
class AudioEncoderPcmU final : public AudioEncoderPcm {
public:
struct Config : public AudioEncoderPcm::Config {
Config() : AudioEncoderPcm::Config(0) {}
};
explicit AudioEncoderPcmU(const Config& config)
: AudioEncoderPcm(config, kSampleRateHz) {}
protected:
size_t EncodeCall(const int16_t* audio,
size_t input_len,
uint8_t* encoded) override;
size_t BytesPerSample() const override;
AudioEncoder::CodecType GetCodecType() const override;
private:
static const int kSampleRateHz = 8000;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioEncoderPcmU);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_CODECS_G711_AUDIO_ENCODER_PCM_H_

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2011 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 <string.h>
#include "modules/third_party/g711/g711.h"
#include "modules/audio_coding/codecs/g711/g711_interface.h"
size_t WebRtcG711_EncodeA(const int16_t* speechIn,
size_t len,
uint8_t* encoded) {
size_t n;
for (n = 0; n < len; n++)
encoded[n] = linear_to_alaw(speechIn[n]);
return len;
}
size_t WebRtcG711_EncodeU(const int16_t* speechIn,
size_t len,
uint8_t* encoded) {
size_t n;
for (n = 0; n < len; n++)
encoded[n] = linear_to_ulaw(speechIn[n]);
return len;
}
size_t WebRtcG711_DecodeA(const uint8_t* encoded,
size_t len,
int16_t* decoded,
int16_t* speechType) {
size_t n;
for (n = 0; n < len; n++)
decoded[n] = alaw_to_linear(encoded[n]);
*speechType = 1;
return len;
}
size_t WebRtcG711_DecodeU(const uint8_t* encoded,
size_t len,
int16_t* decoded,
int16_t* speechType) {
size_t n;
for (n = 0; n < len; n++)
decoded[n] = ulaw_to_linear(encoded[n]);
*speechType = 1;
return len;
}
int16_t WebRtcG711_Version(char* version, int16_t lenBytes) {
strncpy(version, "2.0.0", lenBytes);
return 0;
}

View File

@ -0,0 +1,135 @@
/*
* Copyright (c) 2011 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.
*/
#ifndef MODULES_AUDIO_CODING_CODECS_G711_G711_INTERFACE_H_
#define MODULES_AUDIO_CODING_CODECS_G711_G711_INTERFACE_H_
#include <stdint.h>
// Comfort noise constants
#define G711_WEBRTC_SPEECH 1
#define G711_WEBRTC_CNG 2
#ifdef __cplusplus
extern "C" {
#endif
/****************************************************************************
* WebRtcG711_EncodeA(...)
*
* This function encodes a G711 A-law frame and inserts it into a packet.
* Input speech length has be of any length.
*
* Input:
* - speechIn : Input speech vector
* - len : Samples in speechIn
*
* Output:
* - encoded : The encoded data vector
*
* Return value : Length (in bytes) of coded data.
* Always equal to len input parameter.
*/
size_t WebRtcG711_EncodeA(const int16_t* speechIn,
size_t len,
uint8_t* encoded);
/****************************************************************************
* WebRtcG711_EncodeU(...)
*
* This function encodes a G711 U-law frame and inserts it into a packet.
* Input speech length has be of any length.
*
* Input:
* - speechIn : Input speech vector
* - len : Samples in speechIn
*
* Output:
* - encoded : The encoded data vector
*
* Return value : Length (in bytes) of coded data.
* Always equal to len input parameter.
*/
size_t WebRtcG711_EncodeU(const int16_t* speechIn,
size_t len,
uint8_t* encoded);
/****************************************************************************
* WebRtcG711_DecodeA(...)
*
* This function decodes a packet G711 A-law frame.
*
* Input:
* - encoded : Encoded data
* - len : Bytes in encoded vector
*
* Output:
* - decoded : The decoded vector
* - speechType : 1 normal, 2 CNG (for G711 it should
* always return 1 since G711 does not have a
* built-in DTX/CNG scheme)
*
* Return value : >0 - Samples in decoded vector
* -1 - Error
*/
size_t WebRtcG711_DecodeA(const uint8_t* encoded,
size_t len,
int16_t* decoded,
int16_t* speechType);
/****************************************************************************
* WebRtcG711_DecodeU(...)
*
* This function decodes a packet G711 U-law frame.
*
* Input:
* - encoded : Encoded data
* - len : Bytes in encoded vector
*
* Output:
* - decoded : The decoded vector
* - speechType : 1 normal, 2 CNG (for G711 it should
* always return 1 since G711 does not have a
* built-in DTX/CNG scheme)
*
* Return value : >0 - Samples in decoded vector
* -1 - Error
*/
size_t WebRtcG711_DecodeU(const uint8_t* encoded,
size_t len,
int16_t* decoded,
int16_t* speechType);
/**********************************************************************
* WebRtcG711_Version(...)
*
* This function gives the version string of the G.711 codec.
*
* Input:
* - lenBytes: the size of Allocated space (in Bytes) where
* the version number is written to (in string format).
*
* Output:
* - version: Pointer to a buffer where the version number is
* written to.
*
*/
int16_t WebRtcG711_Version(char* version, int16_t lenBytes);
#ifdef __cplusplus
}
#endif
#endif // MODULES_AUDIO_CODING_CODECS_G711_G711_INTERFACE_H_

View File

@ -0,0 +1,168 @@
/*
* 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.
*/
/*
* testG711.cpp : Defines the entry point for the console application.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* include API */
#include "modules/audio_coding/codecs/g711/g711_interface.h"
/* Runtime statistics */
#include <time.h>
#define CLOCKS_PER_SEC_G711 1000
/* function for reading audio data from PCM file */
bool readframe(int16_t* data, FILE* inp, size_t length) {
size_t rlen = fread(data, sizeof(int16_t), length, inp);
if (rlen >= length)
return false;
memset(data + rlen, 0, (length - rlen) * sizeof(int16_t));
return true;
}
int main(int argc, char* argv[]) {
char inname[80], outname[40], bitname[40];
FILE* inp;
FILE* outp;
FILE* bitp = NULL;
int framecnt;
bool endfile;
size_t framelength = 80;
/* Runtime statistics */
double starttime;
double runtime;
double length_file;
size_t stream_len = 0;
int16_t shortdata[480];
int16_t decoded[480];
uint8_t streamdata[1000];
int16_t speechType[1];
char law[2];
char versionNumber[40];
/* handling wrong input arguments in the command line */
if ((argc != 5) && (argc != 6)) {
printf("\n\nWrong number of arguments or flag values.\n\n");
printf("\n");
printf("\nG.711 test application\n\n");
printf("Usage:\n\n");
printf("./testG711.exe framelength law infile outfile \n\n");
printf("framelength: Framelength in samples.\n");
printf("law : Coding law, A och u.\n");
printf("infile : Normal speech input file\n");
printf("outfile : Speech output file\n\n");
printf("outbits : Output bitstream file [optional]\n\n");
exit(0);
}
/* Get version and print */
WebRtcG711_Version(versionNumber, 40);
printf("-----------------------------------\n");
printf("G.711 version: %s\n\n", versionNumber);
/* Get frame length */
int framelength_int = atoi(argv[1]);
if (framelength_int < 0) {
printf(" G.722: Invalid framelength %d.\n", framelength_int);
exit(1);
}
framelength = static_cast<size_t>(framelength_int);
/* Get compression law */
strcpy(law, argv[2]);
/* Get Input and Output files */
sscanf(argv[3], "%s", inname);
sscanf(argv[4], "%s", outname);
if (argc == 6) {
sscanf(argv[5], "%s", bitname);
if ((bitp = fopen(bitname, "wb")) == NULL) {
printf(" G.711: Cannot read file %s.\n", bitname);
exit(1);
}
}
if ((inp = fopen(inname, "rb")) == NULL) {
printf(" G.711: Cannot read file %s.\n", inname);
exit(1);
}
if ((outp = fopen(outname, "wb")) == NULL) {
printf(" G.711: Cannot write file %s.\n", outname);
exit(1);
}
printf("\nInput: %s\nOutput: %s\n", inname, outname);
if (argc == 6) {
printf("\nBitfile: %s\n", bitname);
}
starttime = clock() / (double)CLOCKS_PER_SEC_G711; /* Runtime statistics */
/* Initialize encoder and decoder */
framecnt = 0;
endfile = false;
while (!endfile) {
framecnt++;
/* Read speech block */
endfile = readframe(shortdata, inp, framelength);
/* G.711 encoding */
if (!strcmp(law, "A")) {
/* A-law encoding */
stream_len = WebRtcG711_EncodeA(shortdata, framelength, streamdata);
if (argc == 6) {
/* Write bits to file */
if (fwrite(streamdata, sizeof(unsigned char), stream_len, bitp) !=
stream_len) {
return -1;
}
}
WebRtcG711_DecodeA(streamdata, stream_len, decoded, speechType);
} else if (!strcmp(law, "u")) {
/* u-law encoding */
stream_len = WebRtcG711_EncodeU(shortdata, framelength, streamdata);
if (argc == 6) {
/* Write bits to file */
if (fwrite(streamdata, sizeof(unsigned char), stream_len, bitp) !=
stream_len) {
return -1;
}
}
WebRtcG711_DecodeU(streamdata, stream_len, decoded, speechType);
} else {
printf("Wrong law mode\n");
exit(1);
}
/* Write coded speech to file */
if (fwrite(decoded, sizeof(short), framelength, outp) != framelength) {
return -1;
}
}
runtime = (double)(clock() / (double)CLOCKS_PER_SEC_G711 - starttime);
length_file = ((double)framecnt * (double)framelength / 8000);
printf("\n\nLength of speech file: %.1f s\n", length_file);
printf("Time to run G.711: %.2f s (%.2f %% of realtime)\n\n", runtime,
(100 * runtime / length_file));
printf("---------------------END----------------------\n");
fclose(inp);
fclose(outp);
return 0;
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) 2015 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/codecs/g722/audio_decoder_g722.h"
#include <string.h>
#include <utility>
#include "modules/audio_coding/codecs/g722/g722_interface.h"
#include "modules/audio_coding/codecs/legacy_encoded_audio_frame.h"
#include "rtc_base/checks.h"
namespace webrtc {
AudioDecoderG722Impl::AudioDecoderG722Impl() {
WebRtcG722_CreateDecoder(&dec_state_);
WebRtcG722_DecoderInit(dec_state_);
}
AudioDecoderG722Impl::~AudioDecoderG722Impl() {
WebRtcG722_FreeDecoder(dec_state_);
}
bool AudioDecoderG722Impl::HasDecodePlc() const {
return false;
}
int AudioDecoderG722Impl::DecodeInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) {
RTC_DCHECK_EQ(SampleRateHz(), sample_rate_hz);
int16_t temp_type = 1; // Default is speech.
size_t ret =
WebRtcG722_Decode(dec_state_, encoded, encoded_len, decoded, &temp_type);
*speech_type = ConvertSpeechType(temp_type);
return static_cast<int>(ret);
}
void AudioDecoderG722Impl::Reset() {
WebRtcG722_DecoderInit(dec_state_);
}
std::vector<AudioDecoder::ParseResult> AudioDecoderG722Impl::ParsePayload(
rtc::Buffer&& payload,
uint32_t timestamp) {
return LegacyEncodedAudioFrame::SplitBySamples(this, std::move(payload),
timestamp, 8, 16);
}
int AudioDecoderG722Impl::PacketDuration(const uint8_t* encoded,
size_t encoded_len) const {
// 1/2 encoded byte per sample per channel.
return static_cast<int>(2 * encoded_len / Channels());
}
int AudioDecoderG722Impl::SampleRateHz() const {
return 16000;
}
size_t AudioDecoderG722Impl::Channels() const {
return 1;
}
AudioDecoderG722StereoImpl::AudioDecoderG722StereoImpl() {
WebRtcG722_CreateDecoder(&dec_state_left_);
WebRtcG722_CreateDecoder(&dec_state_right_);
WebRtcG722_DecoderInit(dec_state_left_);
WebRtcG722_DecoderInit(dec_state_right_);
}
AudioDecoderG722StereoImpl::~AudioDecoderG722StereoImpl() {
WebRtcG722_FreeDecoder(dec_state_left_);
WebRtcG722_FreeDecoder(dec_state_right_);
}
int AudioDecoderG722StereoImpl::DecodeInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) {
RTC_DCHECK_EQ(SampleRateHz(), sample_rate_hz);
int16_t temp_type = 1; // Default is speech.
// De-interleave the bit-stream into two separate payloads.
uint8_t* encoded_deinterleaved = new uint8_t[encoded_len];
SplitStereoPacket(encoded, encoded_len, encoded_deinterleaved);
// Decode left and right.
size_t decoded_len = WebRtcG722_Decode(dec_state_left_, encoded_deinterleaved,
encoded_len / 2, decoded, &temp_type);
size_t ret = WebRtcG722_Decode(
dec_state_right_, &encoded_deinterleaved[encoded_len / 2],
encoded_len / 2, &decoded[decoded_len], &temp_type);
if (ret == decoded_len) {
ret += decoded_len; // Return total number of samples.
// Interleave output.
for (size_t k = ret / 2; k < ret; k++) {
int16_t temp = decoded[k];
memmove(&decoded[2 * k - ret + 2], &decoded[2 * k - ret + 1],
(ret - k - 1) * sizeof(int16_t));
decoded[2 * k - ret + 1] = temp;
}
}
*speech_type = ConvertSpeechType(temp_type);
delete[] encoded_deinterleaved;
return static_cast<int>(ret);
}
int AudioDecoderG722StereoImpl::SampleRateHz() const {
return 16000;
}
size_t AudioDecoderG722StereoImpl::Channels() const {
return 2;
}
void AudioDecoderG722StereoImpl::Reset() {
WebRtcG722_DecoderInit(dec_state_left_);
WebRtcG722_DecoderInit(dec_state_right_);
}
std::vector<AudioDecoder::ParseResult> AudioDecoderG722StereoImpl::ParsePayload(
rtc::Buffer&& payload,
uint32_t timestamp) {
return LegacyEncodedAudioFrame::SplitBySamples(this, std::move(payload),
timestamp, 2 * 8, 16);
}
// Split the stereo packet and place left and right channel after each other
// in the output array.
void AudioDecoderG722StereoImpl::SplitStereoPacket(
const uint8_t* encoded,
size_t encoded_len,
uint8_t* encoded_deinterleaved) {
// Regroup the 4 bits/sample so |l1 l2| |r1 r2| |l3 l4| |r3 r4| ...,
// where "lx" is 4 bits representing left sample number x, and "rx" right
// sample. Two samples fit in one byte, represented with |...|.
for (size_t i = 0; i + 1 < encoded_len; i += 2) {
uint8_t right_byte = ((encoded[i] & 0x0F) << 4) + (encoded[i + 1] & 0x0F);
encoded_deinterleaved[i] = (encoded[i] & 0xF0) + (encoded[i + 1] >> 4);
encoded_deinterleaved[i + 1] = right_byte;
}
// Move one byte representing right channel each loop, and place it at the
// end of the bytestream vector. After looping the data is reordered to:
// |l1 l2| |l3 l4| ... |l(N-1) lN| |r1 r2| |r3 r4| ... |r(N-1) r(N)|,
// where N is the total number of samples.
for (size_t i = 0; i < encoded_len / 2; i++) {
uint8_t right_byte = encoded_deinterleaved[i + 1];
memmove(&encoded_deinterleaved[i + 1], &encoded_deinterleaved[i + 2],
encoded_len - i - 2);
encoded_deinterleaved[encoded_len - 1] = right_byte;
}
}
} // namespace webrtc

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2015 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.
*/
#ifndef MODULES_AUDIO_CODING_CODECS_G722_AUDIO_DECODER_G722_H_
#define MODULES_AUDIO_CODING_CODECS_G722_AUDIO_DECODER_G722_H_
#include "api/audio_codecs/audio_decoder.h"
#include "rtc_base/constructor_magic.h"
typedef struct WebRtcG722DecInst G722DecInst;
namespace webrtc {
class AudioDecoderG722Impl final : public AudioDecoder {
public:
AudioDecoderG722Impl();
~AudioDecoderG722Impl() override;
bool HasDecodePlc() const override;
void Reset() override;
std::vector<ParseResult> ParsePayload(rtc::Buffer&& payload,
uint32_t timestamp) override;
int PacketDuration(const uint8_t* encoded, size_t encoded_len) const override;
int SampleRateHz() const override;
size_t Channels() const override;
protected:
int DecodeInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) override;
private:
G722DecInst* dec_state_;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioDecoderG722Impl);
};
class AudioDecoderG722StereoImpl final : public AudioDecoder {
public:
AudioDecoderG722StereoImpl();
~AudioDecoderG722StereoImpl() override;
void Reset() override;
std::vector<ParseResult> ParsePayload(rtc::Buffer&& payload,
uint32_t timestamp) override;
int SampleRateHz() const override;
size_t Channels() const override;
protected:
int DecodeInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) override;
private:
// Splits the stereo-interleaved payload in |encoded| into separate payloads
// for left and right channels. The separated payloads are written to
// |encoded_deinterleaved|, which must hold at least |encoded_len| samples.
// The left channel starts at offset 0, while the right channel starts at
// offset encoded_len / 2 into |encoded_deinterleaved|.
void SplitStereoPacket(const uint8_t* encoded,
size_t encoded_len,
uint8_t* encoded_deinterleaved);
G722DecInst* dec_state_left_;
G722DecInst* dec_state_right_;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioDecoderG722StereoImpl);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_CODECS_G722_AUDIO_DECODER_G722_H_

View File

@ -0,0 +1,156 @@
/*
* Copyright (c) 2014 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/codecs/g722/audio_encoder_g722.h"
#include <cstdint>
#include "modules/audio_coding/codecs/g722/g722_interface.h"
#include "rtc_base/checks.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
namespace {
const size_t kSampleRateHz = 16000;
} // namespace
AudioEncoderG722Impl::AudioEncoderG722Impl(const AudioEncoderG722Config& config,
int payload_type)
: num_channels_(config.num_channels),
payload_type_(payload_type),
num_10ms_frames_per_packet_(
static_cast<size_t>(config.frame_size_ms / 10)),
num_10ms_frames_buffered_(0),
first_timestamp_in_buffer_(0),
encoders_(new EncoderState[num_channels_]),
interleave_buffer_(2 * num_channels_) {
RTC_CHECK(config.IsOk());
const size_t samples_per_channel =
kSampleRateHz / 100 * num_10ms_frames_per_packet_;
for (size_t i = 0; i < num_channels_; ++i) {
encoders_[i].speech_buffer.reset(new int16_t[samples_per_channel]);
encoders_[i].encoded_buffer.SetSize(samples_per_channel / 2);
}
Reset();
}
AudioEncoderG722Impl::~AudioEncoderG722Impl() = default;
int AudioEncoderG722Impl::SampleRateHz() const {
return kSampleRateHz;
}
size_t AudioEncoderG722Impl::NumChannels() const {
return num_channels_;
}
int AudioEncoderG722Impl::RtpTimestampRateHz() const {
// The RTP timestamp rate for G.722 is 8000 Hz, even though it is a 16 kHz
// codec.
return kSampleRateHz / 2;
}
size_t AudioEncoderG722Impl::Num10MsFramesInNextPacket() const {
return num_10ms_frames_per_packet_;
}
size_t AudioEncoderG722Impl::Max10MsFramesInAPacket() const {
return num_10ms_frames_per_packet_;
}
int AudioEncoderG722Impl::GetTargetBitrate() const {
// 4 bits/sample, 16000 samples/s/channel.
return static_cast<int>(64000 * NumChannels());
}
void AudioEncoderG722Impl::Reset() {
num_10ms_frames_buffered_ = 0;
for (size_t i = 0; i < num_channels_; ++i)
RTC_CHECK_EQ(0, WebRtcG722_EncoderInit(encoders_[i].encoder));
}
absl::optional<std::pair<TimeDelta, TimeDelta>>
AudioEncoderG722Impl::GetFrameLengthRange() const {
return {{TimeDelta::Millis(num_10ms_frames_per_packet_ * 10),
TimeDelta::Millis(num_10ms_frames_per_packet_ * 10)}};
}
AudioEncoder::EncodedInfo AudioEncoderG722Impl::EncodeImpl(
uint32_t rtp_timestamp,
rtc::ArrayView<const int16_t> audio,
rtc::Buffer* encoded) {
if (num_10ms_frames_buffered_ == 0)
first_timestamp_in_buffer_ = rtp_timestamp;
// Deinterleave samples and save them in each channel's buffer.
const size_t start = kSampleRateHz / 100 * num_10ms_frames_buffered_;
for (size_t i = 0; i < kSampleRateHz / 100; ++i)
for (size_t j = 0; j < num_channels_; ++j)
encoders_[j].speech_buffer[start + i] = audio[i * num_channels_ + j];
// If we don't yet have enough samples for a packet, we're done for now.
if (++num_10ms_frames_buffered_ < num_10ms_frames_per_packet_) {
return EncodedInfo();
}
// Encode each channel separately.
RTC_CHECK_EQ(num_10ms_frames_buffered_, num_10ms_frames_per_packet_);
num_10ms_frames_buffered_ = 0;
const size_t samples_per_channel = SamplesPerChannel();
for (size_t i = 0; i < num_channels_; ++i) {
const size_t bytes_encoded = WebRtcG722_Encode(
encoders_[i].encoder, encoders_[i].speech_buffer.get(),
samples_per_channel, encoders_[i].encoded_buffer.data());
RTC_CHECK_EQ(bytes_encoded, samples_per_channel / 2);
}
const size_t bytes_to_encode = samples_per_channel / 2 * num_channels_;
EncodedInfo info;
info.encoded_bytes = encoded->AppendData(
bytes_to_encode, [&](rtc::ArrayView<uint8_t> encoded) {
// Interleave the encoded bytes of the different channels. Each separate
// channel and the interleaved stream encodes two samples per byte, most
// significant half first.
for (size_t i = 0; i < samples_per_channel / 2; ++i) {
for (size_t j = 0; j < num_channels_; ++j) {
uint8_t two_samples = encoders_[j].encoded_buffer.data()[i];
interleave_buffer_.data()[j] = two_samples >> 4;
interleave_buffer_.data()[num_channels_ + j] = two_samples & 0xf;
}
for (size_t j = 0; j < num_channels_; ++j)
encoded[i * num_channels_ + j] =
interleave_buffer_.data()[2 * j] << 4 |
interleave_buffer_.data()[2 * j + 1];
}
return bytes_to_encode;
});
info.encoded_timestamp = first_timestamp_in_buffer_;
info.payload_type = payload_type_;
info.encoder_type = CodecType::kG722;
return info;
}
AudioEncoderG722Impl::EncoderState::EncoderState() {
RTC_CHECK_EQ(0, WebRtcG722_CreateEncoder(&encoder));
}
AudioEncoderG722Impl::EncoderState::~EncoderState() {
RTC_CHECK_EQ(0, WebRtcG722_FreeEncoder(encoder));
}
size_t AudioEncoderG722Impl::SamplesPerChannel() const {
return kSampleRateHz / 100 * num_10ms_frames_per_packet_;
}
} // namespace webrtc

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2014 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.
*/
#ifndef MODULES_AUDIO_CODING_CODECS_G722_AUDIO_ENCODER_G722_H_
#define MODULES_AUDIO_CODING_CODECS_G722_AUDIO_ENCODER_G722_H_
#include <memory>
#include <utility>
#include "absl/types/optional.h"
#include "api/audio_codecs/audio_encoder.h"
#include "api/audio_codecs/g722/audio_encoder_g722_config.h"
#include "api/units/time_delta.h"
#include "modules/audio_coding/codecs/g722/g722_interface.h"
#include "rtc_base/buffer.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
class AudioEncoderG722Impl final : public AudioEncoder {
public:
AudioEncoderG722Impl(const AudioEncoderG722Config& config, int payload_type);
~AudioEncoderG722Impl() override;
int SampleRateHz() const override;
size_t NumChannels() const override;
int RtpTimestampRateHz() const override;
size_t Num10MsFramesInNextPacket() const override;
size_t Max10MsFramesInAPacket() const override;
int GetTargetBitrate() const override;
void Reset() override;
absl::optional<std::pair<TimeDelta, TimeDelta>> GetFrameLengthRange()
const override;
protected:
EncodedInfo EncodeImpl(uint32_t rtp_timestamp,
rtc::ArrayView<const int16_t> audio,
rtc::Buffer* encoded) override;
private:
// The encoder state for one channel.
struct EncoderState {
G722EncInst* encoder;
std::unique_ptr<int16_t[]> speech_buffer; // Queued up for encoding.
rtc::Buffer encoded_buffer; // Already encoded.
EncoderState();
~EncoderState();
};
size_t SamplesPerChannel() const;
const size_t num_channels_;
const int payload_type_;
const size_t num_10ms_frames_per_packet_;
size_t num_10ms_frames_buffered_;
uint32_t first_timestamp_in_buffer_;
const std::unique_ptr<EncoderState[]> encoders_;
rtc::Buffer interleave_buffer_;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioEncoderG722Impl);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_CODECS_G722_AUDIO_ENCODER_G722_H_

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2011 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 <stdlib.h>
#include <string.h>
#include "modules/audio_coding/codecs/g722/g722_interface.h"
#include "modules/third_party/g722/g722_enc_dec.h"
int16_t WebRtcG722_CreateEncoder(G722EncInst **G722enc_inst)
{
*G722enc_inst=(G722EncInst*)malloc(sizeof(G722EncoderState));
if (*G722enc_inst!=NULL) {
return(0);
} else {
return(-1);
}
}
int16_t WebRtcG722_EncoderInit(G722EncInst *G722enc_inst)
{
// Create and/or reset the G.722 encoder
// Bitrate 64 kbps and wideband mode (2)
G722enc_inst = (G722EncInst *) WebRtc_g722_encode_init(
(G722EncoderState*) G722enc_inst, 64000, 2);
if (G722enc_inst == NULL) {
return -1;
} else {
return 0;
}
}
int WebRtcG722_FreeEncoder(G722EncInst *G722enc_inst)
{
// Free encoder memory
return WebRtc_g722_encode_release((G722EncoderState*) G722enc_inst);
}
size_t WebRtcG722_Encode(G722EncInst *G722enc_inst,
const int16_t* speechIn,
size_t len,
uint8_t* encoded)
{
unsigned char *codechar = (unsigned char*) encoded;
// Encode the input speech vector
return WebRtc_g722_encode((G722EncoderState*) G722enc_inst, codechar,
speechIn, len);
}
int16_t WebRtcG722_CreateDecoder(G722DecInst **G722dec_inst)
{
*G722dec_inst=(G722DecInst*)malloc(sizeof(G722DecoderState));
if (*G722dec_inst!=NULL) {
return(0);
} else {
return(-1);
}
}
void WebRtcG722_DecoderInit(G722DecInst* inst) {
// Create and/or reset the G.722 decoder
// Bitrate 64 kbps and wideband mode (2)
WebRtc_g722_decode_init((G722DecoderState*)inst, 64000, 2);
}
int WebRtcG722_FreeDecoder(G722DecInst *G722dec_inst)
{
// Free encoder memory
return WebRtc_g722_decode_release((G722DecoderState*) G722dec_inst);
}
size_t WebRtcG722_Decode(G722DecInst *G722dec_inst,
const uint8_t *encoded,
size_t len,
int16_t *decoded,
int16_t *speechType)
{
// Decode the G.722 encoder stream
*speechType=G722_WEBRTC_SPEECH;
return WebRtc_g722_decode((G722DecoderState*) G722dec_inst, decoded,
encoded, len);
}
int16_t WebRtcG722_Version(char *versionStr, short len)
{
// Get version string
char version[30] = "2.0.0\n";
if (strlen(version) < (unsigned int)len)
{
strcpy(versionStr, version);
return 0;
}
else
{
return -1;
}
}

View File

@ -0,0 +1,173 @@
/*
* Copyright (c) 2011 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.
*/
#ifndef MODULES_AUDIO_CODING_CODECS_G722_G722_INTERFACE_H_
#define MODULES_AUDIO_CODING_CODECS_G722_G722_INTERFACE_H_
#include <stdint.h>
/*
* Solution to support multiple instances
*/
typedef struct WebRtcG722EncInst G722EncInst;
typedef struct WebRtcG722DecInst G722DecInst;
/*
* Comfort noise constants
*/
#define G722_WEBRTC_SPEECH 1
#define G722_WEBRTC_CNG 2
#ifdef __cplusplus
extern "C" {
#endif
/****************************************************************************
* WebRtcG722_CreateEncoder(...)
*
* Create memory used for G722 encoder
*
* Input:
* - G722enc_inst : G722 instance for encoder
*
* Return value : 0 - Ok
* -1 - Error
*/
int16_t WebRtcG722_CreateEncoder(G722EncInst** G722enc_inst);
/****************************************************************************
* WebRtcG722_EncoderInit(...)
*
* This function initializes a G722 instance
*
* Input:
* - G722enc_inst : G722 instance, i.e. the user that should receive
* be initialized
*
* Return value : 0 - Ok
* -1 - Error
*/
int16_t WebRtcG722_EncoderInit(G722EncInst* G722enc_inst);
/****************************************************************************
* WebRtcG722_FreeEncoder(...)
*
* Free the memory used for G722 encoder
*
* Input:
* - G722enc_inst : G722 instance for encoder
*
* Return value : 0 - Ok
* -1 - Error
*/
int WebRtcG722_FreeEncoder(G722EncInst* G722enc_inst);
/****************************************************************************
* WebRtcG722_Encode(...)
*
* This function encodes G722 encoded data.
*
* Input:
* - G722enc_inst : G722 instance, i.e. the user that should encode
* a packet
* - speechIn : Input speech vector
* - len : Samples in speechIn
*
* Output:
* - encoded : The encoded data vector
*
* Return value : Length (in bytes) of coded data
*/
size_t WebRtcG722_Encode(G722EncInst* G722enc_inst,
const int16_t* speechIn,
size_t len,
uint8_t* encoded);
/****************************************************************************
* WebRtcG722_CreateDecoder(...)
*
* Create memory used for G722 encoder
*
* Input:
* - G722dec_inst : G722 instance for decoder
*
* Return value : 0 - Ok
* -1 - Error
*/
int16_t WebRtcG722_CreateDecoder(G722DecInst** G722dec_inst);
/****************************************************************************
* WebRtcG722_DecoderInit(...)
*
* This function initializes a G722 instance
*
* Input:
* - inst : G722 instance
*/
void WebRtcG722_DecoderInit(G722DecInst* inst);
/****************************************************************************
* WebRtcG722_FreeDecoder(...)
*
* Free the memory used for G722 decoder
*
* Input:
* - G722dec_inst : G722 instance for decoder
*
* Return value : 0 - Ok
* -1 - Error
*/
int WebRtcG722_FreeDecoder(G722DecInst* G722dec_inst);
/****************************************************************************
* WebRtcG722_Decode(...)
*
* This function decodes a packet with G729 frame(s). Output speech length
* will be a multiple of 80 samples (80*frames/packet).
*
* Input:
* - G722dec_inst : G722 instance, i.e. the user that should decode
* a packet
* - encoded : Encoded G722 frame(s)
* - len : Bytes in encoded vector
*
* Output:
* - decoded : The decoded vector
* - speechType : 1 normal, 2 CNG (Since G722 does not have its own
* DTX/CNG scheme it should always return 1)
*
* Return value : Samples in decoded vector
*/
size_t WebRtcG722_Decode(G722DecInst* G722dec_inst,
const uint8_t* encoded,
size_t len,
int16_t* decoded,
int16_t* speechType);
/****************************************************************************
* WebRtcG722_Version(...)
*
* Get a string with the current version of the codec
*/
int16_t WebRtcG722_Version(char* versionStr, short len);
#ifdef __cplusplus
}
#endif
#endif /* MODULES_AUDIO_CODING_CODECS_G722_G722_INTERFACE_H_ */

View File

@ -0,0 +1,155 @@
/*
* 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.
*/
/*
* testG722.cpp : Defines the entry point for the console application.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* include API */
#include "modules/audio_coding/codecs/g722/g722_interface.h"
/* Runtime statistics */
#include <time.h>
#define CLOCKS_PER_SEC_G722 100000
// Forward declaration
typedef struct WebRtcG722EncInst G722EncInst;
typedef struct WebRtcG722DecInst G722DecInst;
/* function for reading audio data from PCM file */
bool readframe(int16_t* data, FILE* inp, size_t length) {
size_t rlen = fread(data, sizeof(int16_t), length, inp);
if (rlen >= length)
return false;
memset(data + rlen, 0, (length - rlen) * sizeof(int16_t));
return true;
}
int main(int argc, char* argv[]) {
char inname[60], outbit[40], outname[40];
FILE *inp, *outbitp, *outp;
int framecnt;
bool endfile;
size_t framelength = 160;
G722EncInst* G722enc_inst;
G722DecInst* G722dec_inst;
/* Runtime statistics */
double starttime;
double runtime = 0;
double length_file;
size_t stream_len = 0;
int16_t shortdata[960];
int16_t decoded[960];
uint8_t streamdata[80 * 6];
int16_t speechType[1];
/* handling wrong input arguments in the command line */
if (argc != 5) {
printf("\n\nWrong number of arguments or flag values.\n\n");
printf("\n");
printf("Usage:\n\n");
printf("./testG722.exe framelength infile outbitfile outspeechfile \n\n");
printf("with:\n");
printf("framelength : Framelength in samples.\n\n");
printf("infile : Normal speech input file\n\n");
printf("outbitfile : Bitstream output file\n\n");
printf("outspeechfile: Speech output file\n\n");
exit(0);
}
/* Get frame length */
int framelength_int = atoi(argv[1]);
if (framelength_int < 0) {
printf(" G.722: Invalid framelength %d.\n", framelength_int);
exit(1);
}
framelength = static_cast<size_t>(framelength_int);
/* Get Input and Output files */
sscanf(argv[2], "%s", inname);
sscanf(argv[3], "%s", outbit);
sscanf(argv[4], "%s", outname);
if ((inp = fopen(inname, "rb")) == NULL) {
printf(" G.722: Cannot read file %s.\n", inname);
exit(1);
}
if ((outbitp = fopen(outbit, "wb")) == NULL) {
printf(" G.722: Cannot write file %s.\n", outbit);
exit(1);
}
if ((outp = fopen(outname, "wb")) == NULL) {
printf(" G.722: Cannot write file %s.\n", outname);
exit(1);
}
printf("\nInput:%s\nOutput bitstream:%s\nOutput:%s\n", inname, outbit,
outname);
/* Create and init */
WebRtcG722_CreateEncoder((G722EncInst**)&G722enc_inst);
WebRtcG722_CreateDecoder((G722DecInst**)&G722dec_inst);
WebRtcG722_EncoderInit((G722EncInst*)G722enc_inst);
WebRtcG722_DecoderInit((G722DecInst*)G722dec_inst);
/* Initialize encoder and decoder */
framecnt = 0;
endfile = false;
while (!endfile) {
framecnt++;
/* Read speech block */
endfile = readframe(shortdata, inp, framelength);
/* Start clock before call to encoder and decoder */
starttime = clock() / (double)CLOCKS_PER_SEC_G722;
/* G.722 encoding + decoding */
stream_len = WebRtcG722_Encode((G722EncInst*)G722enc_inst, shortdata,
framelength, streamdata);
WebRtcG722_Decode(G722dec_inst, streamdata, stream_len, decoded,
speechType);
/* Stop clock after call to encoder and decoder */
runtime += (double)((clock() / (double)CLOCKS_PER_SEC_G722) - starttime);
/* Write coded bits to file */
if (fwrite(streamdata, sizeof(short), stream_len / 2, outbitp) !=
stream_len / 2) {
return -1;
}
/* Write coded speech to file */
if (fwrite(decoded, sizeof(short), framelength, outp) != framelength) {
return -1;
}
}
WebRtcG722_FreeEncoder((G722EncInst*)G722enc_inst);
WebRtcG722_FreeDecoder((G722DecInst*)G722dec_inst);
length_file = ((double)framecnt * (double)framelength / 16000);
printf("\n\nLength of speech file: %.1f s\n", length_file);
printf("Time to run G.722: %.2f s (%.2f %% of realtime)\n\n", runtime,
(100 * runtime / length_file));
printf("---------------------END----------------------\n");
fclose(inp);
fclose(outbitp);
fclose(outp);
return 0;
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) 2011 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.
*/
/******************************************************************
iLBC Speech Coder ANSI-C Source Code
WebRtcIlbcfix_AbsQuant.c
******************************************************************/
#include "modules/audio_coding/codecs/ilbc/defines.h"
#include "modules/audio_coding/codecs/ilbc/constants.h"
#include "modules/audio_coding/codecs/ilbc/abs_quant_loop.h"
/*----------------------------------------------------------------*
* predictive noise shaping encoding of scaled start state
* (subrutine for WebRtcIlbcfix_StateSearch)
*---------------------------------------------------------------*/
void WebRtcIlbcfix_AbsQuant(
IlbcEncoder *iLBCenc_inst,
/* (i) Encoder instance */
iLBC_bits *iLBC_encbits, /* (i/o) Encoded bits (outputs idxForMax
and idxVec, uses state_first as
input) */
int16_t *in, /* (i) vector to encode */
int16_t *weightDenum /* (i) denominator of synthesis filter */
) {
int16_t *syntOut;
size_t quantLen[2];
/* Stack based */
int16_t syntOutBuf[LPC_FILTERORDER+STATE_SHORT_LEN_30MS];
int16_t in_weightedVec[STATE_SHORT_LEN_30MS+LPC_FILTERORDER];
int16_t *in_weighted = &in_weightedVec[LPC_FILTERORDER];
/* Initialize the buffers */
WebRtcSpl_MemSetW16(syntOutBuf, 0, LPC_FILTERORDER+STATE_SHORT_LEN_30MS);
syntOut = &syntOutBuf[LPC_FILTERORDER];
/* Start with zero state */
WebRtcSpl_MemSetW16(in_weightedVec, 0, LPC_FILTERORDER);
/* Perform the quantization loop in two sections of length quantLen[i],
where the perceptual weighting filter is updated at the subframe
border */
if (iLBC_encbits->state_first) {
quantLen[0]=SUBL;
quantLen[1]=iLBCenc_inst->state_short_len-SUBL;
} else {
quantLen[0]=iLBCenc_inst->state_short_len-SUBL;
quantLen[1]=SUBL;
}
/* Calculate the weighted residual, switch perceptual weighting
filter at the subframe border */
WebRtcSpl_FilterARFastQ12(
in, in_weighted,
weightDenum, LPC_FILTERORDER+1, quantLen[0]);
WebRtcSpl_FilterARFastQ12(
&in[quantLen[0]], &in_weighted[quantLen[0]],
&weightDenum[LPC_FILTERORDER+1], LPC_FILTERORDER+1, quantLen[1]);
WebRtcIlbcfix_AbsQuantLoop(
syntOut,
in_weighted,
weightDenum,
quantLen,
iLBC_encbits->idxVec);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2011 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.
*/
/******************************************************************
iLBC Speech Coder ANSI-C Source Code
WebRtcIlbcfix_AbsQuant.h
******************************************************************/
#ifndef MODULES_AUDIO_CODING_CODECS_ILBC_MAIN_SOURCE_ABS_QUANT_H_
#define MODULES_AUDIO_CODING_CODECS_ILBC_MAIN_SOURCE_ABS_QUANT_H_
#include "modules/audio_coding/codecs/ilbc/defines.h"
/*----------------------------------------------------------------*
* predictive noise shaping encoding of scaled start state
* (subrutine for WebRtcIlbcfix_StateSearch)
*---------------------------------------------------------------*/
void WebRtcIlbcfix_AbsQuant(
IlbcEncoder* iLBCenc_inst,
/* (i) Encoder instance */
iLBC_bits* iLBC_encbits, /* (i/o) Encoded bits (outputs idxForMax
and idxVec, uses state_first as
input) */
int16_t* in, /* (i) vector to encode */
int16_t* weightDenum /* (i) denominator of synthesis filter */
);
#endif

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) 2011 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.
*/
/******************************************************************
iLBC Speech Coder ANSI-C Source Code
WebRtcIlbcfix_AbsQuantLoop.c
******************************************************************/
#include "modules/audio_coding/codecs/ilbc/defines.h"
#include "modules/audio_coding/codecs/ilbc/constants.h"
#include "modules/audio_coding/codecs/ilbc/sort_sq.h"
void WebRtcIlbcfix_AbsQuantLoop(int16_t *syntOutIN, int16_t *in_weightedIN,
int16_t *weightDenumIN, size_t *quantLenIN,
int16_t *idxVecIN ) {
size_t k1, k2;
int16_t index;
int32_t toQW32;
int32_t toQ32;
int16_t tmp16a;
int16_t xq;
int16_t *syntOut = syntOutIN;
int16_t *in_weighted = in_weightedIN;
int16_t *weightDenum = weightDenumIN;
size_t *quantLen = quantLenIN;
int16_t *idxVec = idxVecIN;
for(k1=0;k1<2;k1++) {
for(k2=0;k2<quantLen[k1];k2++){
/* Filter to get the predicted value */
WebRtcSpl_FilterARFastQ12(
syntOut, syntOut,
weightDenum, LPC_FILTERORDER+1, 1);
/* the quantizer */
toQW32 = (int32_t)(*in_weighted) - (int32_t)(*syntOut);
toQ32 = (((int32_t)toQW32)<<2);
if (toQ32 > 32767) {
toQ32 = (int32_t) 32767;
} else if (toQ32 < -32768) {
toQ32 = (int32_t) -32768;
}
/* Quantize the state */
if (toQW32<(-7577)) {
/* To prevent negative overflow */
index=0;
} else if (toQW32>8151) {
/* To prevent positive overflow */
index=7;
} else {
/* Find the best quantization index
(state_sq3Tbl is in Q13 and toQ is in Q11)
*/
WebRtcIlbcfix_SortSq(&xq, &index,
(int16_t)toQ32,
WebRtcIlbcfix_kStateSq3, 8);
}
/* Store selected index */
(*idxVec++) = index;
/* Compute decoded sample and update of the prediction filter */
tmp16a = ((WebRtcIlbcfix_kStateSq3[index] + 2 ) >> 2);
*syntOut = (int16_t) (tmp16a + (int32_t)(*in_weighted) - toQW32);
syntOut++; in_weighted++;
}
/* Update perceptual weighting filter at subframe border */
weightDenum += 11;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2011 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.
*/
/******************************************************************
iLBC Speech Coder ANSI-C Source Code
WebRtcIlbcfix_AbsQuantLoop.h
******************************************************************/
#ifndef MODULES_AUDIO_CODING_CODECS_ILBC_MAIN_SOURCE_ABS_QUANT_LOOP_H_
#define MODULES_AUDIO_CODING_CODECS_ILBC_MAIN_SOURCE_ABS_QUANT_LOOP_H_
#include "modules/audio_coding/codecs/ilbc/defines.h"
/*----------------------------------------------------------------*
* predictive noise shaping encoding of scaled start state
* (subrutine for WebRtcIlbcfix_StateSearch)
*---------------------------------------------------------------*/
void WebRtcIlbcfix_AbsQuantLoop(int16_t* syntOutIN,
int16_t* in_weightedIN,
int16_t* weightDenumIN,
size_t* quantLenIN,
int16_t* idxVecIN);
#endif

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) 2015 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/codecs/ilbc/audio_decoder_ilbc.h"
#include <memory>
#include <utility>
#include "modules/audio_coding/codecs/ilbc/ilbc.h"
#include "modules/audio_coding/codecs/legacy_encoded_audio_frame.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
AudioDecoderIlbcImpl::AudioDecoderIlbcImpl() {
WebRtcIlbcfix_DecoderCreate(&dec_state_);
WebRtcIlbcfix_Decoderinit30Ms(dec_state_);
}
AudioDecoderIlbcImpl::~AudioDecoderIlbcImpl() {
WebRtcIlbcfix_DecoderFree(dec_state_);
}
bool AudioDecoderIlbcImpl::HasDecodePlc() const {
return true;
}
int AudioDecoderIlbcImpl::DecodeInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) {
RTC_DCHECK_EQ(sample_rate_hz, 8000);
int16_t temp_type = 1; // Default is speech.
int ret = WebRtcIlbcfix_Decode(dec_state_, encoded, encoded_len, decoded,
&temp_type);
*speech_type = ConvertSpeechType(temp_type);
return ret;
}
size_t AudioDecoderIlbcImpl::DecodePlc(size_t num_frames, int16_t* decoded) {
return WebRtcIlbcfix_NetEqPlc(dec_state_, decoded, num_frames);
}
void AudioDecoderIlbcImpl::Reset() {
WebRtcIlbcfix_Decoderinit30Ms(dec_state_);
}
std::vector<AudioDecoder::ParseResult> AudioDecoderIlbcImpl::ParsePayload(
rtc::Buffer&& payload,
uint32_t timestamp) {
std::vector<ParseResult> results;
size_t bytes_per_frame;
int timestamps_per_frame;
if (payload.size() >= 950) {
RTC_LOG(LS_WARNING)
<< "AudioDecoderIlbcImpl::ParsePayload: Payload too large";
return results;
}
if (payload.size() % 38 == 0) {
// 20 ms frames.
bytes_per_frame = 38;
timestamps_per_frame = 160;
} else if (payload.size() % 50 == 0) {
// 30 ms frames.
bytes_per_frame = 50;
timestamps_per_frame = 240;
} else {
RTC_LOG(LS_WARNING)
<< "AudioDecoderIlbcImpl::ParsePayload: Invalid payload";
return results;
}
RTC_DCHECK_EQ(0, payload.size() % bytes_per_frame);
if (payload.size() == bytes_per_frame) {
std::unique_ptr<EncodedAudioFrame> frame(
new LegacyEncodedAudioFrame(this, std::move(payload)));
results.emplace_back(timestamp, 0, std::move(frame));
} else {
size_t byte_offset;
uint32_t timestamp_offset;
for (byte_offset = 0, timestamp_offset = 0; byte_offset < payload.size();
byte_offset += bytes_per_frame,
timestamp_offset += timestamps_per_frame) {
std::unique_ptr<EncodedAudioFrame> frame(new LegacyEncodedAudioFrame(
this, rtc::Buffer(payload.data() + byte_offset, bytes_per_frame)));
results.emplace_back(timestamp + timestamp_offset, 0, std::move(frame));
}
}
return results;
}
int AudioDecoderIlbcImpl::SampleRateHz() const {
return 8000;
}
size_t AudioDecoderIlbcImpl::Channels() const {
return 1;
}
} // namespace webrtc

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2015 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.
*/
#ifndef MODULES_AUDIO_CODING_CODECS_ILBC_AUDIO_DECODER_ILBC_H_
#define MODULES_AUDIO_CODING_CODECS_ILBC_AUDIO_DECODER_ILBC_H_
#include <stddef.h>
#include <stdint.h>
#include <vector>
#include "api/audio_codecs/audio_decoder.h"
#include "rtc_base/buffer.h"
#include "rtc_base/constructor_magic.h"
typedef struct iLBC_decinst_t_ IlbcDecoderInstance;
namespace webrtc {
class AudioDecoderIlbcImpl final : public AudioDecoder {
public:
AudioDecoderIlbcImpl();
~AudioDecoderIlbcImpl() override;
bool HasDecodePlc() const override;
size_t DecodePlc(size_t num_frames, int16_t* decoded) override;
void Reset() override;
std::vector<ParseResult> ParsePayload(rtc::Buffer&& payload,
uint32_t timestamp) override;
int SampleRateHz() const override;
size_t Channels() const override;
protected:
int DecodeInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) override;
private:
IlbcDecoderInstance* dec_state_;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioDecoderIlbcImpl);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_CODECS_ILBC_AUDIO_DECODER_ILBC_H_

View File

@ -0,0 +1,151 @@
/*
* Copyright (c) 2014 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/codecs/ilbc/audio_encoder_ilbc.h"
#include <algorithm>
#include <cstdint>
#include "modules/audio_coding/codecs/ilbc/ilbc.h"
#include "rtc_base/checks.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
namespace {
const int kSampleRateHz = 8000;
int GetIlbcBitrate(int ptime) {
switch (ptime) {
case 20:
case 40:
// 38 bytes per frame of 20 ms => 15200 bits/s.
return 15200;
case 30:
case 60:
// 50 bytes per frame of 30 ms => (approx) 13333 bits/s.
return 13333;
default:
FATAL();
}
}
} // namespace
AudioEncoderIlbcImpl::AudioEncoderIlbcImpl(const AudioEncoderIlbcConfig& config,
int payload_type)
: frame_size_ms_(config.frame_size_ms),
payload_type_(payload_type),
num_10ms_frames_per_packet_(
static_cast<size_t>(config.frame_size_ms / 10)),
encoder_(nullptr) {
RTC_CHECK(config.IsOk());
Reset();
}
AudioEncoderIlbcImpl::~AudioEncoderIlbcImpl() {
RTC_CHECK_EQ(0, WebRtcIlbcfix_EncoderFree(encoder_));
}
int AudioEncoderIlbcImpl::SampleRateHz() const {
return kSampleRateHz;
}
size_t AudioEncoderIlbcImpl::NumChannels() const {
return 1;
}
size_t AudioEncoderIlbcImpl::Num10MsFramesInNextPacket() const {
return num_10ms_frames_per_packet_;
}
size_t AudioEncoderIlbcImpl::Max10MsFramesInAPacket() const {
return num_10ms_frames_per_packet_;
}
int AudioEncoderIlbcImpl::GetTargetBitrate() const {
return GetIlbcBitrate(rtc::dchecked_cast<int>(num_10ms_frames_per_packet_) *
10);
}
AudioEncoder::EncodedInfo AudioEncoderIlbcImpl::EncodeImpl(
uint32_t rtp_timestamp,
rtc::ArrayView<const int16_t> audio,
rtc::Buffer* encoded) {
// Save timestamp if starting a new packet.
if (num_10ms_frames_buffered_ == 0)
first_timestamp_in_buffer_ = rtp_timestamp;
// Buffer input.
std::copy(audio.cbegin(), audio.cend(),
input_buffer_ + kSampleRateHz / 100 * num_10ms_frames_buffered_);
// If we don't yet have enough buffered input for a whole packet, we're done
// for now.
if (++num_10ms_frames_buffered_ < num_10ms_frames_per_packet_) {
return EncodedInfo();
}
// Encode buffered input.
RTC_DCHECK_EQ(num_10ms_frames_buffered_, num_10ms_frames_per_packet_);
num_10ms_frames_buffered_ = 0;
size_t encoded_bytes = encoded->AppendData(
RequiredOutputSizeBytes(), [&](rtc::ArrayView<uint8_t> encoded) {
const int r = WebRtcIlbcfix_Encode(
encoder_, input_buffer_,
kSampleRateHz / 100 * num_10ms_frames_per_packet_, encoded.data());
RTC_CHECK_GE(r, 0);
return static_cast<size_t>(r);
});
RTC_DCHECK_EQ(encoded_bytes, RequiredOutputSizeBytes());
EncodedInfo info;
info.encoded_bytes = encoded_bytes;
info.encoded_timestamp = first_timestamp_in_buffer_;
info.payload_type = payload_type_;
info.encoder_type = CodecType::kIlbc;
return info;
}
void AudioEncoderIlbcImpl::Reset() {
if (encoder_)
RTC_CHECK_EQ(0, WebRtcIlbcfix_EncoderFree(encoder_));
RTC_CHECK_EQ(0, WebRtcIlbcfix_EncoderCreate(&encoder_));
const int encoder_frame_size_ms =
frame_size_ms_ > 30 ? frame_size_ms_ / 2 : frame_size_ms_;
RTC_CHECK_EQ(0, WebRtcIlbcfix_EncoderInit(encoder_, encoder_frame_size_ms));
num_10ms_frames_buffered_ = 0;
}
absl::optional<std::pair<TimeDelta, TimeDelta>>
AudioEncoderIlbcImpl::GetFrameLengthRange() const {
return {{TimeDelta::Millis(num_10ms_frames_per_packet_ * 10),
TimeDelta::Millis(num_10ms_frames_per_packet_ * 10)}};
}
size_t AudioEncoderIlbcImpl::RequiredOutputSizeBytes() const {
switch (num_10ms_frames_per_packet_) {
case 2:
return 38;
case 3:
return 50;
case 4:
return 2 * 38;
case 6:
return 2 * 50;
default:
FATAL();
}
}
} // namespace webrtc

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2014 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.
*/
#ifndef MODULES_AUDIO_CODING_CODECS_ILBC_AUDIO_ENCODER_ILBC_H_
#define MODULES_AUDIO_CODING_CODECS_ILBC_AUDIO_ENCODER_ILBC_H_
#include <utility>
#include "absl/types/optional.h"
#include "api/audio_codecs/audio_encoder.h"
#include "api/audio_codecs/ilbc/audio_encoder_ilbc_config.h"
#include "api/units/time_delta.h"
#include "modules/audio_coding/codecs/ilbc/ilbc.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
class AudioEncoderIlbcImpl final : public AudioEncoder {
public:
AudioEncoderIlbcImpl(const AudioEncoderIlbcConfig& config, int payload_type);
~AudioEncoderIlbcImpl() override;
int SampleRateHz() const override;
size_t NumChannels() const override;
size_t Num10MsFramesInNextPacket() const override;
size_t Max10MsFramesInAPacket() const override;
int GetTargetBitrate() const override;
EncodedInfo EncodeImpl(uint32_t rtp_timestamp,
rtc::ArrayView<const int16_t> audio,
rtc::Buffer* encoded) override;
void Reset() override;
absl::optional<std::pair<TimeDelta, TimeDelta>> GetFrameLengthRange()
const override;
private:
size_t RequiredOutputSizeBytes() const;
static constexpr size_t kMaxSamplesPerPacket = 480;
const int frame_size_ms_;
const int payload_type_;
const size_t num_10ms_frames_per_packet_;
size_t num_10ms_frames_buffered_;
uint32_t first_timestamp_in_buffer_;
int16_t input_buffer_[kMaxSamplesPerPacket];
IlbcEncoderInstance* encoder_;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioEncoderIlbcImpl);
};
} // namespace webrtc
#endif // MODULES_AUDIO_CODING_CODECS_ILBC_AUDIO_ENCODER_ILBC_H_

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2011 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.
*/
/******************************************************************
iLBC Speech Coder ANSI-C Source Code
WebRtcIlbcfix_AugmentedCbCorr.c
******************************************************************/
#include "modules/audio_coding/codecs/ilbc/defines.h"
#include "modules/audio_coding/codecs/ilbc/constants.h"
#include "modules/audio_coding/codecs/ilbc/augmented_cb_corr.h"
void WebRtcIlbcfix_AugmentedCbCorr(
int16_t *target, /* (i) Target vector */
int16_t *buffer, /* (i) Memory buffer */
int16_t *interpSamples, /* (i) buffer with
interpolated samples */
int32_t *crossDot, /* (o) The cross correlation between
the target and the Augmented
vector */
size_t low, /* (i) Lag to start from (typically
20) */
size_t high, /* (i) Lag to end at (typically 39) */
int scale) /* (i) Scale factor to use for
the crossDot */
{
size_t lagcount;
size_t ilow;
int16_t *targetPtr;
int32_t *crossDotPtr;
int16_t *iSPtr=interpSamples;
/* Calculate the correlation between the target and the
interpolated codebook. The correlation is calculated in
3 sections with the interpolated part in the middle */
crossDotPtr=crossDot;
for (lagcount=low; lagcount<=high; lagcount++) {
ilow = lagcount - 4;
/* Compute dot product for the first (lagcount-4) samples */
(*crossDotPtr) = WebRtcSpl_DotProductWithScale(target, buffer-lagcount, ilow, scale);
/* Compute dot product on the interpolated samples */
(*crossDotPtr) += WebRtcSpl_DotProductWithScale(target+ilow, iSPtr, 4, scale);
targetPtr = target + lagcount;
iSPtr += lagcount-ilow;
/* Compute dot product for the remaining samples */
(*crossDotPtr) += WebRtcSpl_DotProductWithScale(targetPtr, buffer-lagcount, SUBL-lagcount, scale);
crossDotPtr++;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2011 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.
*/
/******************************************************************
iLBC Speech Coder ANSI-C Source Code
WebRtcIlbcfix_AugmentedCbCorr.h
******************************************************************/
#ifndef MODULES_AUDIO_CODING_CODECS_ILBC_MAIN_SOURCE_AUGMENTED_CB_CORR_H_
#define MODULES_AUDIO_CODING_CODECS_ILBC_MAIN_SOURCE_AUGMENTED_CB_CORR_H_
#include "modules/audio_coding/codecs/ilbc/defines.h"
/*----------------------------------------------------------------*
* Calculate correlation between target and Augmented codebooks
*---------------------------------------------------------------*/
void WebRtcIlbcfix_AugmentedCbCorr(
int16_t* target, /* (i) Target vector */
int16_t* buffer, /* (i) Memory buffer */
int16_t* interpSamples, /* (i) buffer with
interpolated samples */
int32_t* crossDot, /* (o) The cross correlation between
the target and the Augmented
vector */
size_t low, /* (i) Lag to start from (typically
20) */
size_t high, /* (i) Lag to end at (typically 39 */
int scale); /* (i) Scale factor to use for the crossDot */
#endif

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2011 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.
*/
/******************************************************************
iLBC Speech Coder ANSI-C Source Code
WebRtcIlbcfix_BwExpand.c
******************************************************************/
#include "modules/audio_coding/codecs/ilbc/defines.h"
/*----------------------------------------------------------------*
* lpc bandwidth expansion
*---------------------------------------------------------------*/
/* The output is in the same domain as the input */
void WebRtcIlbcfix_BwExpand(
int16_t *out, /* (o) the bandwidth expanded lpc coefficients */
int16_t *in, /* (i) the lpc coefficients before bandwidth
expansion */
int16_t *coef, /* (i) the bandwidth expansion factor Q15 */
int16_t length /* (i) the length of lpc coefficient vectors */
) {
int i;
out[0] = in[0];
for (i = 1; i < length; i++) {
/* out[i] = coef[i] * in[i] with rounding.
in[] and out[] are in Q12 and coef[] is in Q15
*/
out[i] = (int16_t)((coef[i] * in[i] + 16384) >> 15);
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2011 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.
*/
/******************************************************************
iLBC Speech Coder ANSI-C Source Code
WebRtcIlbcfix_BwExpand.h
******************************************************************/
#ifndef MODULES_AUDIO_CODING_CODECS_ILBC_MAIN_SOURCE_BW_EXPAND_H_
#define MODULES_AUDIO_CODING_CODECS_ILBC_MAIN_SOURCE_BW_EXPAND_H_
#include "modules/audio_coding/codecs/ilbc/defines.h"
/*----------------------------------------------------------------*
* lpc bandwidth expansion
*---------------------------------------------------------------*/
void WebRtcIlbcfix_BwExpand(
int16_t* out, /* (o) the bandwidth expanded lpc coefficients */
int16_t* in, /* (i) the lpc coefficients before bandwidth
expansion */
int16_t* coef, /* (i) the bandwidth expansion factor Q15 */
int16_t length /* (i) the length of lpc coefficient vectors */
);
#endif

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) 2011 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.
*/
/******************************************************************
iLBC Speech Coder ANSI-C Source Code
WebRtcIlbcfix_CbConstruct.c
******************************************************************/
#include "modules/audio_coding/codecs/ilbc/cb_construct.h"
#include "modules/audio_coding/codecs/ilbc/defines.h"
#include "modules/audio_coding/codecs/ilbc/gain_dequant.h"
#include "modules/audio_coding/codecs/ilbc/get_cd_vec.h"
#include "rtc_base/sanitizer.h"
// An arithmetic operation that is allowed to overflow. (It's still undefined
// behavior, so not a good idea; this just makes UBSan ignore the violation, so
// that our old code can continue to do what it's always been doing.)
static inline int32_t RTC_NO_SANITIZE("signed-integer-overflow")
OverflowingAddS32S32ToS32(int32_t a, int32_t b) {
return a + b;
}
/*----------------------------------------------------------------*
* Construct decoded vector from codebook and gains.
*---------------------------------------------------------------*/
bool WebRtcIlbcfix_CbConstruct(
int16_t* decvector, /* (o) Decoded vector */
const int16_t* index, /* (i) Codebook indices */
const int16_t* gain_index, /* (i) Gain quantization indices */
int16_t* mem, /* (i) Buffer for codevector construction */
size_t lMem, /* (i) Length of buffer */
size_t veclen) { /* (i) Length of vector */
size_t j;
int16_t gain[CB_NSTAGES];
/* Stack based */
int16_t cbvec0[SUBL];
int16_t cbvec1[SUBL];
int16_t cbvec2[SUBL];
int32_t a32;
int16_t *gainPtr;
/* gain de-quantization */
gain[0] = WebRtcIlbcfix_GainDequant(gain_index[0], 16384, 0);
gain[1] = WebRtcIlbcfix_GainDequant(gain_index[1], gain[0], 1);
gain[2] = WebRtcIlbcfix_GainDequant(gain_index[2], gain[1], 2);
/* codebook vector construction and construction of total vector */
/* Stack based */
if (!WebRtcIlbcfix_GetCbVec(cbvec0, mem, (size_t)index[0], lMem, veclen))
return false; // Failure.
if (!WebRtcIlbcfix_GetCbVec(cbvec1, mem, (size_t)index[1], lMem, veclen))
return false; // Failure.
if (!WebRtcIlbcfix_GetCbVec(cbvec2, mem, (size_t)index[2], lMem, veclen))
return false; // Failure.
gainPtr = &gain[0];
for (j=0;j<veclen;j++) {
a32 = (*gainPtr++) * cbvec0[j];
a32 += (*gainPtr++) * cbvec1[j];
a32 = OverflowingAddS32S32ToS32(a32, (*gainPtr) * cbvec2[j]);
gainPtr -= 2;
decvector[j] = (int16_t)((a32 + 8192) >> 14);
}
return true; // Success.
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2011 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.
*/
/******************************************************************
iLBC Speech Coder ANSI-C Source Code
WebRtcIlbcfix_CbConstruct.h
******************************************************************/
#ifndef MODULES_AUDIO_CODING_CODECS_ILBC_MAIN_SOURCE_CB_CONSTRUCT_H_
#define MODULES_AUDIO_CODING_CODECS_ILBC_MAIN_SOURCE_CB_CONSTRUCT_H_
#include <stdbool.h>
#include <stdint.h>
#include "modules/audio_coding/codecs/ilbc/defines.h"
#include "rtc_base/system/unused.h"
/*----------------------------------------------------------------*
* Construct decoded vector from codebook and gains.
*---------------------------------------------------------------*/
// Returns true on success, false on failure.
bool WebRtcIlbcfix_CbConstruct(
int16_t* decvector, /* (o) Decoded vector */
const int16_t* index, /* (i) Codebook indices */
const int16_t* gain_index, /* (i) Gain quantization indices */
int16_t* mem, /* (i) Buffer for codevector construction */
size_t lMem, /* (i) Length of buffer */
size_t veclen /* (i) Length of vector */
) RTC_WARN_UNUSED_RESULT;
#endif

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2011 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.
*/
/******************************************************************
iLBC Speech Coder ANSI-C Source Code
WebRtcIlbcfix_CbMemEnergy.c
******************************************************************/
#include "modules/audio_coding/codecs/ilbc/defines.h"
#include "modules/audio_coding/codecs/ilbc/constants.h"
#include "modules/audio_coding/codecs/ilbc/cb_mem_energy_calc.h"
/*----------------------------------------------------------------*
* Function WebRtcIlbcfix_CbMemEnergy computes the energy of all
* the vectors in the codebook memory that will be used in the
* following search for the best match.
*----------------------------------------------------------------*/
void WebRtcIlbcfix_CbMemEnergy(
size_t range,
int16_t *CB, /* (i) The CB memory (1:st section) */
int16_t *filteredCB, /* (i) The filtered CB memory (2:nd section) */
size_t lMem, /* (i) Length of the CB memory */
size_t lTarget, /* (i) Length of the target vector */
int16_t *energyW16, /* (o) Energy in the CB vectors */
int16_t *energyShifts, /* (o) Shift value of the energy */
int scale, /* (i) The scaling of all energy values */
size_t base_size /* (i) Index to where energy values should be stored */
) {
int16_t *ppi, *ppo, *pp;
int32_t energy, tmp32;
/* Compute the energy and store it in a vector. Also the
* corresponding shift values are stored. The energy values
* are reused in all three stages. */
/* Calculate the energy in the first block of 'lTarget' sampels. */
ppi = CB+lMem-lTarget-1;
ppo = CB+lMem-1;
pp=CB+lMem-lTarget;
energy = WebRtcSpl_DotProductWithScale( pp, pp, lTarget, scale);
/* Normalize the energy and store the number of shifts */
energyShifts[0] = (int16_t)WebRtcSpl_NormW32(energy);
tmp32 = energy << energyShifts[0];
energyW16[0] = (int16_t)(tmp32 >> 16);
/* Compute the energy of the rest of the cb memory
* by step wise adding and subtracting the next
* sample and the last sample respectively. */
WebRtcIlbcfix_CbMemEnergyCalc(energy, range, ppi, ppo, energyW16, energyShifts, scale, 0);
/* Next, precompute the energy values for the filtered cb section */
energy=0;
pp=filteredCB+lMem-lTarget;
energy = WebRtcSpl_DotProductWithScale( pp, pp, lTarget, scale);
/* Normalize the energy and store the number of shifts */
energyShifts[base_size] = (int16_t)WebRtcSpl_NormW32(energy);
tmp32 = energy << energyShifts[base_size];
energyW16[base_size] = (int16_t)(tmp32 >> 16);
ppi = filteredCB + lMem - 1 - lTarget;
ppo = filteredCB + lMem - 1;
WebRtcIlbcfix_CbMemEnergyCalc(energy, range, ppi, ppo, energyW16, energyShifts, scale, base_size);
}

Some files were not shown because too many files have changed in this diff Show More