Merge branch 'upstream-master'
Bug: 153469641 Test: none Change-Id: Ic33e363deb0d1ac4bb4d57c3c239eb2e45370802
This commit is contained in:
2086
modules/audio_coding/BUILD.gn
Normal file
2086
modules/audio_coding/BUILD.gn
Normal file
File diff suppressed because it is too large
Load Diff
7
modules/audio_coding/DEPS
Normal file
7
modules/audio_coding/DEPS
Normal 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",
|
||||
]
|
||||
4
modules/audio_coding/OWNERS.webrtc
Normal file
4
modules/audio_coding/OWNERS.webrtc
Normal file
@ -0,0 +1,4 @@
|
||||
henrik.lundin@webrtc.org
|
||||
kwiberg@webrtc.org
|
||||
minyue@webrtc.org
|
||||
ivoc@webrtc.org
|
||||
161
modules/audio_coding/acm2/acm_receive_test.cc
Normal file
161
modules/audio_coding/acm2/acm_receive_test.cc
Normal 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
|
||||
98
modules/audio_coding/acm2/acm_receive_test.h
Normal file
98
modules/audio_coding/acm2/acm_receive_test.h
Normal 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_
|
||||
336
modules/audio_coding/acm2/acm_receiver.cc
Normal file
336
modules/audio_coding/acm2/acm_receiver.cc
Normal 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
|
||||
229
modules/audio_coding/acm2/acm_receiver.h
Normal file
229
modules/audio_coding/acm2/acm_receiver.h
Normal 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_
|
||||
464
modules/audio_coding/acm2/acm_receiver_unittest.cc
Normal file
464
modules/audio_coding/acm2/acm_receiver_unittest.cc
Normal 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
|
||||
114
modules/audio_coding/acm2/acm_remixing.cc
Normal file
114
modules/audio_coding/acm2/acm_remixing.cc
Normal 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
|
||||
34
modules/audio_coding/acm2/acm_remixing.h
Normal file
34
modules/audio_coding/acm2/acm_remixing.h
Normal 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_
|
||||
191
modules/audio_coding/acm2/acm_remixing_unittest.cc
Normal file
191
modules/audio_coding/acm2/acm_remixing_unittest.cc
Normal 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
|
||||
62
modules/audio_coding/acm2/acm_resampler.cc
Normal file
62
modules/audio_coding/acm2/acm_resampler.cc
Normal 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
|
||||
41
modules/audio_coding/acm2/acm_resampler.h
Normal file
41
modules/audio_coding/acm2/acm_resampler.h
Normal 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_
|
||||
173
modules/audio_coding/acm2/acm_send_test.cc
Normal file
173
modules/audio_coding/acm2/acm_send_test.cc
Normal 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
|
||||
90
modules/audio_coding/acm2/acm_send_test.h
Normal file
90
modules/audio_coding/acm2/acm_send_test.h
Normal 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_
|
||||
626
modules/audio_coding/acm2/audio_coding_module.cc
Normal file
626
modules/audio_coding/acm2/audio_coding_module.cc
Normal 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
|
||||
1899
modules/audio_coding/acm2/audio_coding_module_unittest.cc
Normal file
1899
modules/audio_coding/acm2/audio_coding_module_unittest.cc
Normal file
File diff suppressed because it is too large
Load Diff
63
modules/audio_coding/acm2/call_statistics.cc
Normal file
63
modules/audio_coding/acm2/call_statistics.cc
Normal 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
|
||||
64
modules/audio_coding/acm2/call_statistics.h
Normal file
64
modules/audio_coding/acm2/call_statistics.h
Normal 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_
|
||||
57
modules/audio_coding/acm2/call_statistics_unittest.cc
Normal file
57
modules/audio_coding/acm2/call_statistics_unittest.cc
Normal 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
|
||||
33
modules/audio_coding/audio_coding.gni
Normal file
33
modules/audio_coding/audio_coding.gni
Normal 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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
180
modules/audio_coding/audio_network_adaptor/config.proto
Normal file
180
modules/audio_coding/audio_network_adaptor/config.proto
Normal 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;
|
||||
}
|
||||
|
||||
19
modules/audio_coding/audio_network_adaptor/controller.cc
Normal file
19
modules/audio_coding/audio_network_adaptor/controller.cc
Normal 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
|
||||
42
modules/audio_coding/audio_network_adaptor/controller.h
Normal file
42
modules/audio_coding/audio_network_adaptor/controller.h
Normal 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_
|
||||
437
modules/audio_coding/audio_network_adaptor/controller_manager.cc
Normal file
437
modules/audio_coding/audio_network_adaptor/controller_manager.cc
Normal 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
|
||||
123
modules/audio_coding/audio_network_adaptor/controller_manager.h
Normal file
123
modules/audio_coding/audio_network_adaptor/controller_manager.h
Normal 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_
|
||||
@ -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
|
||||
42
modules/audio_coding/audio_network_adaptor/debug_dump.proto
Normal file
42
modules/audio_coding/audio_network_adaptor/debug_dump.proto
Normal 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;
|
||||
}
|
||||
|
||||
163
modules/audio_coding/audio_network_adaptor/debug_dump_writer.cc
Normal file
163
modules/audio_coding/audio_network_adaptor/debug_dump_writer.cc
Normal 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
|
||||
@ -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_
|
||||
51
modules/audio_coding/audio_network_adaptor/dtx_controller.cc
Normal file
51
modules/audio_coding/audio_network_adaptor/dtx_controller.cc
Normal 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
|
||||
51
modules/audio_coding/audio_network_adaptor/dtx_controller.h
Normal file
51
modules/audio_coding/audio_network_adaptor/dtx_controller.h
Normal 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_
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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_
|
||||
@ -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_
|
||||
@ -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_
|
||||
@ -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_
|
||||
@ -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_
|
||||
147
modules/audio_coding/audio_network_adaptor/parse_ana_dump.py
Executable file
147
modules/audio_coding/audio_network_adaptor/parse_ana_dump.py
Executable 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()
|
||||
@ -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_
|
||||
@ -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
|
||||
20
modules/audio_coding/codecs/audio_decoder.h
Normal file
20
modules/audio_coding/codecs/audio_decoder.h
Normal 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_
|
||||
20
modules/audio_coding/codecs/audio_encoder.h
Normal file
20
modules/audio_coding/codecs/audio_encoder.h
Normal 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_
|
||||
@ -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
|
||||
@ -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
|
||||
323
modules/audio_coding/codecs/cng/audio_encoder_cng.cc
Normal file
323
modules/audio_coding/codecs/cng/audio_encoder_cng.cc
Normal 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
|
||||
49
modules/audio_coding/codecs/cng/audio_encoder_cng.h
Normal file
49
modules/audio_coding/codecs/cng/audio_encoder_cng.h
Normal 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_
|
||||
520
modules/audio_coding/codecs/cng/audio_encoder_cng_unittest.cc
Normal file
520
modules/audio_coding/codecs/cng/audio_encoder_cng_unittest.cc
Normal 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
|
||||
250
modules/audio_coding/codecs/cng/cng_unittest.cc
Normal file
250
modules/audio_coding/codecs/cng/cng_unittest.cc
Normal 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
|
||||
436
modules/audio_coding/codecs/cng/webrtc_cng.cc
Normal file
436
modules/audio_coding/codecs/cng/webrtc_cng.cc
Normal 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
|
||||
99
modules/audio_coding/codecs/cng/webrtc_cng.h
Normal file
99
modules/audio_coding/codecs/cng/webrtc_cng.h
Normal 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_
|
||||
90
modules/audio_coding/codecs/g711/audio_decoder_pcm.cc
Normal file
90
modules/audio_coding/codecs/g711/audio_decoder_pcm.cc
Normal 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
|
||||
76
modules/audio_coding/codecs/g711/audio_decoder_pcm.h
Normal file
76
modules/audio_coding/codecs/g711/audio_decoder_pcm.h
Normal 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_
|
||||
126
modules/audio_coding/codecs/g711/audio_encoder_pcm.cc
Normal file
126
modules/audio_coding/codecs/g711/audio_encoder_pcm.cc
Normal 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
|
||||
125
modules/audio_coding/codecs/g711/audio_encoder_pcm.h
Normal file
125
modules/audio_coding/codecs/g711/audio_encoder_pcm.h
Normal 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_
|
||||
59
modules/audio_coding/codecs/g711/g711_interface.c
Normal file
59
modules/audio_coding/codecs/g711/g711_interface.c
Normal 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;
|
||||
}
|
||||
135
modules/audio_coding/codecs/g711/g711_interface.h
Normal file
135
modules/audio_coding/codecs/g711/g711_interface.h
Normal 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_
|
||||
168
modules/audio_coding/codecs/g711/test/testG711.cc
Normal file
168
modules/audio_coding/codecs/g711/test/testG711.cc
Normal 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;
|
||||
}
|
||||
164
modules/audio_coding/codecs/g722/audio_decoder_g722.cc
Normal file
164
modules/audio_coding/codecs/g722/audio_decoder_g722.cc
Normal 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
|
||||
79
modules/audio_coding/codecs/g722/audio_decoder_g722.h
Normal file
79
modules/audio_coding/codecs/g722/audio_decoder_g722.h
Normal 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_
|
||||
156
modules/audio_coding/codecs/g722/audio_encoder_g722.cc
Normal file
156
modules/audio_coding/codecs/g722/audio_encoder_g722.cc
Normal 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
|
||||
70
modules/audio_coding/codecs/g722/audio_encoder_g722.h
Normal file
70
modules/audio_coding/codecs/g722/audio_encoder_g722.h
Normal 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_
|
||||
104
modules/audio_coding/codecs/g722/g722_interface.c
Normal file
104
modules/audio_coding/codecs/g722/g722_interface.c
Normal 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;
|
||||
}
|
||||
}
|
||||
173
modules/audio_coding/codecs/g722/g722_interface.h
Normal file
173
modules/audio_coding/codecs/g722/g722_interface.h
Normal 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_ */
|
||||
155
modules/audio_coding/codecs/g722/test/testG722.cc
Normal file
155
modules/audio_coding/codecs/g722/test/testG722.cc
Normal 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;
|
||||
}
|
||||
80
modules/audio_coding/codecs/ilbc/abs_quant.c
Normal file
80
modules/audio_coding/codecs/ilbc/abs_quant.c
Normal 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);
|
||||
|
||||
}
|
||||
39
modules/audio_coding/codecs/ilbc/abs_quant.h
Normal file
39
modules/audio_coding/codecs/ilbc/abs_quant.h
Normal 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
|
||||
87
modules/audio_coding/codecs/ilbc/abs_quant_loop.c
Normal file
87
modules/audio_coding/codecs/ilbc/abs_quant_loop.c
Normal 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;
|
||||
}
|
||||
}
|
||||
35
modules/audio_coding/codecs/ilbc/abs_quant_loop.h
Normal file
35
modules/audio_coding/codecs/ilbc/abs_quant_loop.h
Normal 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
|
||||
110
modules/audio_coding/codecs/ilbc/audio_decoder_ilbc.cc
Normal file
110
modules/audio_coding/codecs/ilbc/audio_decoder_ilbc.cc
Normal 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
|
||||
51
modules/audio_coding/codecs/ilbc/audio_decoder_ilbc.h
Normal file
51
modules/audio_coding/codecs/ilbc/audio_decoder_ilbc.h
Normal 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_
|
||||
151
modules/audio_coding/codecs/ilbc/audio_encoder_ilbc.cc
Normal file
151
modules/audio_coding/codecs/ilbc/audio_encoder_ilbc.cc
Normal 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
|
||||
57
modules/audio_coding/codecs/ilbc/audio_encoder_ilbc.h
Normal file
57
modules/audio_coding/codecs/ilbc/audio_encoder_ilbc.h
Normal 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_
|
||||
63
modules/audio_coding/codecs/ilbc/augmented_cb_corr.c
Normal file
63
modules/audio_coding/codecs/ilbc/augmented_cb_corr.c
Normal 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++;
|
||||
}
|
||||
}
|
||||
41
modules/audio_coding/codecs/ilbc/augmented_cb_corr.h
Normal file
41
modules/audio_coding/codecs/ilbc/augmented_cb_corr.h
Normal 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
|
||||
42
modules/audio_coding/codecs/ilbc/bw_expand.c
Normal file
42
modules/audio_coding/codecs/ilbc/bw_expand.c
Normal 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);
|
||||
}
|
||||
}
|
||||
36
modules/audio_coding/codecs/ilbc/bw_expand.h
Normal file
36
modules/audio_coding/codecs/ilbc/bw_expand.h
Normal 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
|
||||
80
modules/audio_coding/codecs/ilbc/cb_construct.c
Normal file
80
modules/audio_coding/codecs/ilbc/cb_construct.c
Normal 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.
|
||||
}
|
||||
42
modules/audio_coding/codecs/ilbc/cb_construct.h
Normal file
42
modules/audio_coding/codecs/ilbc/cb_construct.h
Normal 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
|
||||
79
modules/audio_coding/codecs/ilbc/cb_mem_energy.c
Normal file
79
modules/audio_coding/codecs/ilbc/cb_mem_energy.c
Normal 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
Reference in New Issue
Block a user