Moving src/webrtc into src/.

In order to eliminate the WebRTC Subtree mirror in Chromium, 
WebRTC is moving the content of the src/webrtc directory up
to the src/ directory.

NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
TBR=tommi@webrtc.org

Bug: chromium:611808
Change-Id: Iac59c5b51b950f174119565bac87955a7994bc38
Reviewed-on: https://webrtc-review.googlesource.com/1560
Commit-Queue: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Henrik Kjellander <kjellander@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#19845}
This commit is contained in:
Mirko Bonadei
2017-09-15 06:15:48 +02:00
committed by Commit Bot
parent 6674846b4a
commit bb547203bf
4576 changed files with 1092 additions and 1196 deletions

View File

@ -0,0 +1,168 @@
/*
* 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 "webrtc/modules/audio_coding/codecs/opus/audio_decoder_opus.h"
#include <utility>
#include "webrtc/rtc_base/checks.h"
namespace webrtc {
namespace {
class OpusFrame : public AudioDecoder::EncodedAudioFrame {
public:
OpusFrame(AudioDecoderOpusImpl* decoder,
rtc::Buffer&& payload,
bool is_primary_payload)
: decoder_(decoder),
payload_(std::move(payload)),
is_primary_payload_(is_primary_payload) {}
size_t Duration() const override {
int ret;
if (is_primary_payload_) {
ret = decoder_->PacketDuration(payload_.data(), payload_.size());
} else {
ret = decoder_->PacketDurationRedundant(payload_.data(), payload_.size());
}
return (ret < 0) ? 0 : static_cast<size_t>(ret);
}
rtc::Optional<DecodeResult> Decode(
rtc::ArrayView<int16_t> decoded) const override {
AudioDecoder::SpeechType speech_type = AudioDecoder::kSpeech;
int ret;
if (is_primary_payload_) {
ret = decoder_->Decode(
payload_.data(), payload_.size(), decoder_->SampleRateHz(),
decoded.size() * sizeof(int16_t), decoded.data(), &speech_type);
} else {
ret = decoder_->DecodeRedundant(
payload_.data(), payload_.size(), decoder_->SampleRateHz(),
decoded.size() * sizeof(int16_t), decoded.data(), &speech_type);
}
if (ret < 0)
return rtc::Optional<DecodeResult>();
return rtc::Optional<DecodeResult>({static_cast<size_t>(ret), speech_type});
}
private:
AudioDecoderOpusImpl* const decoder_;
const rtc::Buffer payload_;
const bool is_primary_payload_;
};
} // namespace
AudioDecoderOpusImpl::AudioDecoderOpusImpl(size_t num_channels)
: channels_(num_channels) {
RTC_DCHECK(num_channels == 1 || num_channels == 2);
WebRtcOpus_DecoderCreate(&dec_state_, channels_);
WebRtcOpus_DecoderInit(dec_state_);
}
AudioDecoderOpusImpl::~AudioDecoderOpusImpl() {
WebRtcOpus_DecoderFree(dec_state_);
}
std::vector<AudioDecoder::ParseResult> AudioDecoderOpusImpl::ParsePayload(
rtc::Buffer&& payload,
uint32_t timestamp) {
std::vector<ParseResult> results;
if (PacketHasFec(payload.data(), payload.size())) {
const int duration =
PacketDurationRedundant(payload.data(), payload.size());
RTC_DCHECK_GE(duration, 0);
rtc::Buffer payload_copy(payload.data(), payload.size());
std::unique_ptr<EncodedAudioFrame> fec_frame(
new OpusFrame(this, std::move(payload_copy), false));
results.emplace_back(timestamp - duration, 1, std::move(fec_frame));
}
std::unique_ptr<EncodedAudioFrame> frame(
new OpusFrame(this, std::move(payload), true));
results.emplace_back(timestamp, 0, std::move(frame));
return results;
}
int AudioDecoderOpusImpl::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, 48000);
int16_t temp_type = 1; // Default is speech.
int ret =
WebRtcOpus_Decode(dec_state_, encoded, encoded_len, decoded, &temp_type);
if (ret > 0)
ret *= static_cast<int>(channels_); // Return total number of samples.
*speech_type = ConvertSpeechType(temp_type);
return ret;
}
int AudioDecoderOpusImpl::DecodeRedundantInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) {
if (!PacketHasFec(encoded, encoded_len)) {
// This packet is a RED packet.
return DecodeInternal(encoded, encoded_len, sample_rate_hz, decoded,
speech_type);
}
RTC_DCHECK_EQ(sample_rate_hz, 48000);
int16_t temp_type = 1; // Default is speech.
int ret = WebRtcOpus_DecodeFec(dec_state_, encoded, encoded_len, decoded,
&temp_type);
if (ret > 0)
ret *= static_cast<int>(channels_); // Return total number of samples.
*speech_type = ConvertSpeechType(temp_type);
return ret;
}
void AudioDecoderOpusImpl::Reset() {
WebRtcOpus_DecoderInit(dec_state_);
}
int AudioDecoderOpusImpl::PacketDuration(const uint8_t* encoded,
size_t encoded_len) const {
return WebRtcOpus_DurationEst(dec_state_, encoded, encoded_len);
}
int AudioDecoderOpusImpl::PacketDurationRedundant(const uint8_t* encoded,
size_t encoded_len) const {
if (!PacketHasFec(encoded, encoded_len)) {
// This packet is a RED packet.
return PacketDuration(encoded, encoded_len);
}
return WebRtcOpus_FecDurationEst(encoded, encoded_len);
}
bool AudioDecoderOpusImpl::PacketHasFec(const uint8_t* encoded,
size_t encoded_len) const {
int fec;
fec = WebRtcOpus_PacketHasFec(encoded, encoded_len);
return (fec == 1);
}
int AudioDecoderOpusImpl::SampleRateHz() const {
return 48000;
}
size_t AudioDecoderOpusImpl::Channels() const {
return channels_;
}
} // namespace webrtc

View File

@ -0,0 +1,55 @@
/*
* 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 WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_AUDIO_DECODER_OPUS_H_
#define WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_AUDIO_DECODER_OPUS_H_
#include "webrtc/api/audio_codecs/audio_decoder.h"
#include "webrtc/modules/audio_coding/codecs/opus/opus_interface.h"
#include "webrtc/rtc_base/constructormagic.h"
namespace webrtc {
class AudioDecoderOpusImpl final : public AudioDecoder {
public:
explicit AudioDecoderOpusImpl(size_t num_channels);
~AudioDecoderOpusImpl() override;
std::vector<ParseResult> ParsePayload(rtc::Buffer&& payload,
uint32_t timestamp) override;
void Reset() override;
int PacketDuration(const uint8_t* encoded, size_t encoded_len) const override;
int PacketDurationRedundant(const uint8_t* encoded,
size_t encoded_len) const override;
bool PacketHasFec(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;
int DecodeRedundantInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) override;
private:
OpusDecInst* dec_state_;
const size_t channels_;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioDecoderOpusImpl);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_AUDIO_DECODER_OPUS_H_

View File

@ -0,0 +1,783 @@
/*
* 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 "webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.h"
#include <algorithm>
#include <iterator>
#include <utility>
#include "webrtc/common_types.h"
#include "webrtc/modules/audio_coding/audio_network_adaptor/audio_network_adaptor_impl.h"
#include "webrtc/modules/audio_coding/audio_network_adaptor/controller_manager.h"
#include "webrtc/modules/audio_coding/codecs/opus/opus_interface.h"
#include "webrtc/rtc_base/arraysize.h"
#include "webrtc/rtc_base/checks.h"
#include "webrtc/rtc_base/logging.h"
#include "webrtc/rtc_base/numerics/exp_filter.h"
#include "webrtc/rtc_base/protobuf_utils.h"
#include "webrtc/rtc_base/ptr_util.h"
#include "webrtc/rtc_base/safe_conversions.h"
#include "webrtc/rtc_base/safe_minmax.h"
#include "webrtc/rtc_base/string_to_number.h"
#include "webrtc/rtc_base/timeutils.h"
#include "webrtc/system_wrappers/include/field_trial.h"
namespace webrtc {
namespace {
// Codec parameters for Opus.
// draft-spittka-payload-rtp-opus-03
// Recommended bitrates:
// 8-12 kb/s for NB speech,
// 16-20 kb/s for WB speech,
// 28-40 kb/s for FB speech,
// 48-64 kb/s for FB mono music, and
// 64-128 kb/s for FB stereo music.
// The current implementation applies the following values to mono signals,
// and multiplies them by 2 for stereo.
constexpr int kOpusBitrateNbBps = 12000;
constexpr int kOpusBitrateWbBps = 20000;
constexpr int kOpusBitrateFbBps = 32000;
constexpr int kSampleRateHz = 48000;
constexpr int kDefaultMaxPlaybackRate = 48000;
// These two lists must be sorted from low to high
#if WEBRTC_OPUS_SUPPORT_120MS_PTIME
constexpr int kANASupportedFrameLengths[] = {20, 60, 120};
constexpr int kOpusSupportedFrameLengths[] = {10, 20, 40, 60, 120};
#else
constexpr int kANASupportedFrameLengths[] = {20, 60};
constexpr int kOpusSupportedFrameLengths[] = {10, 20, 40, 60};
#endif
// PacketLossFractionSmoother uses an exponential filter with a time constant
// of -1.0 / ln(0.9999) = 10000 ms.
constexpr float kAlphaForPacketLossFractionSmoother = 0.9999f;
// Optimize the loss rate to configure Opus. Basically, optimized loss rate is
// the input loss rate rounded down to various levels, because a robustly good
// audio quality is achieved by lowering the packet loss down.
// Additionally, to prevent toggling, margins are used, i.e., when jumping to
// a loss rate from below, a higher threshold is used than jumping to the same
// level from above.
float OptimizePacketLossRate(float new_loss_rate, float old_loss_rate) {
RTC_DCHECK_GE(new_loss_rate, 0.0f);
RTC_DCHECK_LE(new_loss_rate, 1.0f);
RTC_DCHECK_GE(old_loss_rate, 0.0f);
RTC_DCHECK_LE(old_loss_rate, 1.0f);
constexpr float kPacketLossRate20 = 0.20f;
constexpr float kPacketLossRate10 = 0.10f;
constexpr float kPacketLossRate5 = 0.05f;
constexpr float kPacketLossRate1 = 0.01f;
constexpr float kLossRate20Margin = 0.02f;
constexpr float kLossRate10Margin = 0.01f;
constexpr float kLossRate5Margin = 0.01f;
if (new_loss_rate >=
kPacketLossRate20 +
kLossRate20Margin *
(kPacketLossRate20 - old_loss_rate > 0 ? 1 : -1)) {
return kPacketLossRate20;
} else if (new_loss_rate >=
kPacketLossRate10 +
kLossRate10Margin *
(kPacketLossRate10 - old_loss_rate > 0 ? 1 : -1)) {
return kPacketLossRate10;
} else if (new_loss_rate >=
kPacketLossRate5 +
kLossRate5Margin *
(kPacketLossRate5 - old_loss_rate > 0 ? 1 : -1)) {
return kPacketLossRate5;
} else if (new_loss_rate >= kPacketLossRate1) {
return kPacketLossRate1;
} else {
return 0.0f;
}
}
rtc::Optional<std::string> GetFormatParameter(const SdpAudioFormat& format,
const std::string& param) {
auto it = format.parameters.find(param);
return (it == format.parameters.end())
? rtc::Optional<std::string>()
: rtc::Optional<std::string>(it->second);
}
template <typename T>
rtc::Optional<T> GetFormatParameter(const SdpAudioFormat& format,
const std::string& param) {
return rtc::StringToNumber<T>(GetFormatParameter(format, param).value_or(""));
}
int CalculateDefaultBitrate(int max_playback_rate, size_t num_channels) {
const int bitrate = [&] {
if (max_playback_rate <= 8000) {
return kOpusBitrateNbBps * rtc::dchecked_cast<int>(num_channels);
} else if (max_playback_rate <= 16000) {
return kOpusBitrateWbBps * rtc::dchecked_cast<int>(num_channels);
} else {
return kOpusBitrateFbBps * rtc::dchecked_cast<int>(num_channels);
}
}();
RTC_DCHECK_GE(bitrate, AudioEncoderOpusConfig::kMinBitrateBps);
RTC_DCHECK_LE(bitrate, AudioEncoderOpusConfig::kMaxBitrateBps);
return bitrate;
}
// Get the maxaveragebitrate parameter in string-form, so we can properly figure
// out how invalid it is and accurately log invalid values.
int CalculateBitrate(int max_playback_rate_hz,
size_t num_channels,
rtc::Optional<std::string> bitrate_param) {
const int default_bitrate =
CalculateDefaultBitrate(max_playback_rate_hz, num_channels);
if (bitrate_param) {
const auto bitrate = rtc::StringToNumber<int>(*bitrate_param);
if (bitrate) {
const int chosen_bitrate =
std::max(AudioEncoderOpusConfig::kMinBitrateBps,
std::min(*bitrate, AudioEncoderOpusConfig::kMaxBitrateBps));
if (bitrate != chosen_bitrate) {
LOG(LS_WARNING) << "Invalid maxaveragebitrate " << *bitrate
<< " clamped to " << chosen_bitrate;
}
return chosen_bitrate;
}
LOG(LS_WARNING) << "Invalid maxaveragebitrate \"" << *bitrate_param
<< "\" replaced by default bitrate " << default_bitrate;
}
return default_bitrate;
}
int GetChannelCount(const SdpAudioFormat& format) {
const auto param = GetFormatParameter(format, "stereo");
if (param == "1") {
return 2;
} else {
return 1;
}
}
int GetMaxPlaybackRate(const SdpAudioFormat& format) {
const auto param = GetFormatParameter<int>(format, "maxplaybackrate");
if (param && *param >= 8000) {
return std::min(*param, kDefaultMaxPlaybackRate);
}
return kDefaultMaxPlaybackRate;
}
int GetFrameSizeMs(const SdpAudioFormat& format) {
const auto ptime = GetFormatParameter<int>(format, "ptime");
if (ptime) {
// Pick the next highest supported frame length from
// kOpusSupportedFrameLengths.
for (const int supported_frame_length : kOpusSupportedFrameLengths) {
if (supported_frame_length >= *ptime) {
return supported_frame_length;
}
}
// If none was found, return the largest supported frame length.
return *(std::end(kOpusSupportedFrameLengths) - 1);
}
return AudioEncoderOpusConfig::kDefaultFrameSizeMs;
}
void FindSupportedFrameLengths(int min_frame_length_ms,
int max_frame_length_ms,
std::vector<int>* out) {
out->clear();
std::copy_if(std::begin(kANASupportedFrameLengths),
std::end(kANASupportedFrameLengths), std::back_inserter(*out),
[&](int frame_length_ms) {
return frame_length_ms >= min_frame_length_ms &&
frame_length_ms <= max_frame_length_ms;
});
RTC_DCHECK(std::is_sorted(out->begin(), out->end()));
}
int GetBitrateBps(const AudioEncoderOpusConfig& config) {
RTC_DCHECK(config.IsOk());
return *config.bitrate_bps;
}
} // namespace
void AudioEncoderOpus::AppendSupportedEncoders(
std::vector<AudioCodecSpec>* specs) {
const SdpAudioFormat fmt = {
"opus", 48000, 2, {{"minptime", "10"}, {"useinbandfec", "1"}}};
const AudioCodecInfo info = QueryAudioEncoder(*SdpToConfig(fmt));
specs->push_back({fmt, info});
}
AudioCodecInfo AudioEncoderOpus::QueryAudioEncoder(
const AudioEncoderOpusConfig& config) {
RTC_DCHECK(config.IsOk());
AudioCodecInfo info(48000, config.num_channels, *config.bitrate_bps,
AudioEncoderOpusConfig::kMinBitrateBps,
AudioEncoderOpusConfig::kMaxBitrateBps);
info.allow_comfort_noise = false;
info.supports_network_adaption = true;
return info;
}
std::unique_ptr<AudioEncoder> AudioEncoderOpus::MakeAudioEncoder(
const AudioEncoderOpusConfig& config,
int payload_type) {
RTC_DCHECK(config.IsOk());
return rtc::MakeUnique<AudioEncoderOpus>(config, payload_type);
}
rtc::Optional<AudioCodecInfo> AudioEncoderOpus::QueryAudioEncoder(
const SdpAudioFormat& format) {
if (STR_CASE_CMP(format.name.c_str(), GetPayloadName()) == 0 &&
format.clockrate_hz == 48000 && format.num_channels == 2) {
const size_t num_channels = GetChannelCount(format);
const int bitrate =
CalculateBitrate(GetMaxPlaybackRate(format), num_channels,
GetFormatParameter(format, "maxaveragebitrate"));
AudioCodecInfo info(48000, num_channels, bitrate,
AudioEncoderOpusConfig::kMinBitrateBps,
AudioEncoderOpusConfig::kMaxBitrateBps);
info.allow_comfort_noise = false;
info.supports_network_adaption = true;
return rtc::Optional<AudioCodecInfo>(info);
}
return rtc::Optional<AudioCodecInfo>();
}
AudioEncoderOpusConfig AudioEncoderOpus::CreateConfig(
int payload_type,
const SdpAudioFormat& format) {
auto opt_config = SdpToConfig(format);
RTC_CHECK(opt_config);
opt_config->payload_type = payload_type;
return *opt_config;
}
AudioEncoderOpusConfig AudioEncoderOpus::CreateConfig(
const CodecInst& codec_inst) {
AudioEncoderOpusConfig config;
config.frame_size_ms = rtc::CheckedDivExact(codec_inst.pacsize, 48);
config.num_channels = codec_inst.channels;
config.bitrate_bps = rtc::Optional<int>(codec_inst.rate);
config.application = config.num_channels == 1
? AudioEncoderOpusConfig::ApplicationMode::kVoip
: AudioEncoderOpusConfig::ApplicationMode::kAudio;
config.supported_frame_lengths_ms.push_back(config.frame_size_ms);
return config;
}
rtc::Optional<AudioEncoderOpusConfig> AudioEncoderOpus::SdpToConfig(
const SdpAudioFormat& format) {
if (STR_CASE_CMP(format.name.c_str(), "opus") != 0 ||
format.clockrate_hz != 48000 || format.num_channels != 2) {
return rtc::Optional<AudioEncoderOpusConfig>();
}
AudioEncoderOpusConfig config;
config.num_channels = GetChannelCount(format);
config.frame_size_ms = GetFrameSizeMs(format);
config.max_playback_rate_hz = GetMaxPlaybackRate(format);
config.fec_enabled = (GetFormatParameter(format, "useinbandfec") == "1");
config.dtx_enabled = (GetFormatParameter(format, "usedtx") == "1");
config.cbr_enabled = (GetFormatParameter(format, "cbr") == "1");
config.bitrate_bps = rtc::Optional<int>(
CalculateBitrate(config.max_playback_rate_hz, config.num_channels,
GetFormatParameter(format, "maxaveragebitrate")));
config.application = config.num_channels == 1
? AudioEncoderOpusConfig::ApplicationMode::kVoip
: AudioEncoderOpusConfig::ApplicationMode::kAudio;
constexpr int kMinANAFrameLength = kANASupportedFrameLengths[0];
constexpr int kMaxANAFrameLength =
kANASupportedFrameLengths[arraysize(kANASupportedFrameLengths) - 1];
// For now, minptime and maxptime are only used with ANA. If ptime is outside
// of this range, it will get adjusted once ANA takes hold. Ideally, we'd know
// if ANA was to be used when setting up the config, and adjust accordingly.
const int min_frame_length_ms =
GetFormatParameter<int>(format, "minptime").value_or(kMinANAFrameLength);
const int max_frame_length_ms =
GetFormatParameter<int>(format, "maxptime").value_or(kMaxANAFrameLength);
FindSupportedFrameLengths(min_frame_length_ms, max_frame_length_ms,
&config.supported_frame_lengths_ms);
RTC_DCHECK(config.IsOk());
return rtc::Optional<AudioEncoderOpusConfig>(config);
}
rtc::Optional<int> AudioEncoderOpus::GetNewComplexity(
const AudioEncoderOpusConfig& config) {
RTC_DCHECK(config.IsOk());
const int bitrate_bps = GetBitrateBps(config);
if (bitrate_bps >= config.complexity_threshold_bps -
config.complexity_threshold_window_bps &&
bitrate_bps <= config.complexity_threshold_bps +
config.complexity_threshold_window_bps) {
// Within the hysteresis window; make no change.
return rtc::Optional<int>();
} else {
return rtc::Optional<int>(bitrate_bps <= config.complexity_threshold_bps
? config.low_rate_complexity
: config.complexity);
}
}
class AudioEncoderOpus::PacketLossFractionSmoother {
public:
explicit PacketLossFractionSmoother()
: last_sample_time_ms_(rtc::TimeMillis()),
smoother_(kAlphaForPacketLossFractionSmoother) {}
// Gets the smoothed packet loss fraction.
float GetAverage() const {
float value = smoother_.filtered();
return (value == rtc::ExpFilter::kValueUndefined) ? 0.0f : value;
}
// Add new observation to the packet loss fraction smoother.
void AddSample(float packet_loss_fraction) {
int64_t now_ms = rtc::TimeMillis();
smoother_.Apply(static_cast<float>(now_ms - last_sample_time_ms_),
packet_loss_fraction);
last_sample_time_ms_ = now_ms;
}
private:
int64_t last_sample_time_ms_;
// An exponential filter is used to smooth the packet loss fraction.
rtc::ExpFilter smoother_;
};
AudioEncoderOpus::AudioEncoderOpus(const AudioEncoderOpusConfig& config)
: AudioEncoderOpus(config, config.payload_type) {}
AudioEncoderOpus::AudioEncoderOpus(const AudioEncoderOpusConfig& config,
int payload_type)
: AudioEncoderOpus(
config,
payload_type,
[this](const ProtoString& config_string, RtcEventLog* event_log) {
return DefaultAudioNetworkAdaptorCreator(config_string, event_log);
},
// We choose 5sec as initial time constant due to empirical data.
rtc::MakeUnique<SmoothingFilterImpl>(5000)) {}
AudioEncoderOpus::AudioEncoderOpus(
const AudioEncoderOpusConfig& config,
int payload_type,
const AudioNetworkAdaptorCreator& audio_network_adaptor_creator,
std::unique_ptr<SmoothingFilter> bitrate_smoother)
: payload_type_(payload_type),
send_side_bwe_with_overhead_(
webrtc::field_trial::IsEnabled("WebRTC-SendSideBwe-WithOverhead")),
packet_loss_rate_(0.0),
inst_(nullptr),
packet_loss_fraction_smoother_(new PacketLossFractionSmoother()),
audio_network_adaptor_creator_(audio_network_adaptor_creator),
bitrate_smoother_(std::move(bitrate_smoother)) {
RTC_DCHECK(0 <= payload_type && payload_type <= 127);
// Sanity check of the redundant payload type field that we want to get rid
// of. See https://bugs.chromium.org/p/webrtc/issues/detail?id=7847
RTC_CHECK(config.payload_type == -1 || config.payload_type == payload_type);
RTC_CHECK(RecreateEncoderInstance(config));
}
AudioEncoderOpus::AudioEncoderOpus(const CodecInst& codec_inst)
: AudioEncoderOpus(CreateConfig(codec_inst), codec_inst.pltype) {}
AudioEncoderOpus::AudioEncoderOpus(int payload_type,
const SdpAudioFormat& format)
: AudioEncoderOpus(*SdpToConfig(format), payload_type) {}
AudioEncoderOpus::~AudioEncoderOpus() {
RTC_CHECK_EQ(0, WebRtcOpus_EncoderFree(inst_));
}
int AudioEncoderOpus::SampleRateHz() const {
return kSampleRateHz;
}
size_t AudioEncoderOpus::NumChannels() const {
return config_.num_channels;
}
size_t AudioEncoderOpus::Num10MsFramesInNextPacket() const {
return Num10msFramesPerPacket();
}
size_t AudioEncoderOpus::Max10MsFramesInAPacket() const {
return Num10msFramesPerPacket();
}
int AudioEncoderOpus::GetTargetBitrate() const {
return GetBitrateBps(config_);
}
void AudioEncoderOpus::Reset() {
RTC_CHECK(RecreateEncoderInstance(config_));
}
bool AudioEncoderOpus::SetFec(bool enable) {
if (enable) {
RTC_CHECK_EQ(0, WebRtcOpus_EnableFec(inst_));
} else {
RTC_CHECK_EQ(0, WebRtcOpus_DisableFec(inst_));
}
config_.fec_enabled = enable;
return true;
}
bool AudioEncoderOpus::SetDtx(bool enable) {
if (enable) {
RTC_CHECK_EQ(0, WebRtcOpus_EnableDtx(inst_));
} else {
RTC_CHECK_EQ(0, WebRtcOpus_DisableDtx(inst_));
}
config_.dtx_enabled = enable;
return true;
}
bool AudioEncoderOpus::GetDtx() const {
return config_.dtx_enabled;
}
bool AudioEncoderOpus::SetApplication(Application application) {
auto conf = config_;
switch (application) {
case Application::kSpeech:
conf.application = AudioEncoderOpusConfig::ApplicationMode::kVoip;
break;
case Application::kAudio:
conf.application = AudioEncoderOpusConfig::ApplicationMode::kAudio;
break;
}
return RecreateEncoderInstance(conf);
}
void AudioEncoderOpus::SetMaxPlaybackRate(int frequency_hz) {
auto conf = config_;
conf.max_playback_rate_hz = frequency_hz;
RTC_CHECK(RecreateEncoderInstance(conf));
}
bool AudioEncoderOpus::EnableAudioNetworkAdaptor(
const std::string& config_string,
RtcEventLog* event_log) {
audio_network_adaptor_ =
audio_network_adaptor_creator_(config_string, event_log);
return audio_network_adaptor_.get() != nullptr;
}
void AudioEncoderOpus::DisableAudioNetworkAdaptor() {
audio_network_adaptor_.reset(nullptr);
}
void AudioEncoderOpus::OnReceivedUplinkPacketLossFraction(
float uplink_packet_loss_fraction) {
if (!audio_network_adaptor_) {
packet_loss_fraction_smoother_->AddSample(uplink_packet_loss_fraction);
float average_fraction_loss = packet_loss_fraction_smoother_->GetAverage();
return SetProjectedPacketLossRate(average_fraction_loss);
}
audio_network_adaptor_->SetUplinkPacketLossFraction(
uplink_packet_loss_fraction);
ApplyAudioNetworkAdaptor();
}
void AudioEncoderOpus::OnReceivedUplinkRecoverablePacketLossFraction(
float uplink_recoverable_packet_loss_fraction) {
if (!audio_network_adaptor_)
return;
audio_network_adaptor_->SetUplinkRecoverablePacketLossFraction(
uplink_recoverable_packet_loss_fraction);
ApplyAudioNetworkAdaptor();
}
void AudioEncoderOpus::OnReceivedUplinkBandwidth(
int target_audio_bitrate_bps,
rtc::Optional<int64_t> bwe_period_ms) {
if (audio_network_adaptor_) {
audio_network_adaptor_->SetTargetAudioBitrate(target_audio_bitrate_bps);
// We give smoothed bitrate allocation to audio network adaptor as
// the uplink bandwidth.
// The BWE spikes should not affect the bitrate smoother more than 25%.
// To simplify the calculations we use a step response as input signal.
// The step response of an exponential filter is
// u(t) = 1 - e^(-t / time_constant).
// In order to limit the affect of a BWE spike within 25% of its value
// before
// the next BWE update, we would choose a time constant that fulfills
// 1 - e^(-bwe_period_ms / time_constant) < 0.25
// Then 4 * bwe_period_ms is a good choice.
if (bwe_period_ms)
bitrate_smoother_->SetTimeConstantMs(*bwe_period_ms * 4);
bitrate_smoother_->AddSample(target_audio_bitrate_bps);
ApplyAudioNetworkAdaptor();
} else if (send_side_bwe_with_overhead_) {
if (!overhead_bytes_per_packet_) {
LOG(LS_INFO)
<< "AudioEncoderOpus: Overhead unknown, target audio bitrate "
<< target_audio_bitrate_bps << " bps is ignored.";
return;
}
const int overhead_bps = static_cast<int>(
*overhead_bytes_per_packet_ * 8 * 100 / Num10MsFramesInNextPacket());
SetTargetBitrate(
std::min(AudioEncoderOpusConfig::kMaxBitrateBps,
std::max(AudioEncoderOpusConfig::kMinBitrateBps,
target_audio_bitrate_bps - overhead_bps)));
} else {
SetTargetBitrate(target_audio_bitrate_bps);
}
}
void AudioEncoderOpus::OnReceivedRtt(int rtt_ms) {
if (!audio_network_adaptor_)
return;
audio_network_adaptor_->SetRtt(rtt_ms);
ApplyAudioNetworkAdaptor();
}
void AudioEncoderOpus::OnReceivedOverhead(size_t overhead_bytes_per_packet) {
if (audio_network_adaptor_) {
audio_network_adaptor_->SetOverhead(overhead_bytes_per_packet);
ApplyAudioNetworkAdaptor();
} else {
overhead_bytes_per_packet_ =
rtc::Optional<size_t>(overhead_bytes_per_packet);
}
}
void AudioEncoderOpus::SetReceiverFrameLengthRange(int min_frame_length_ms,
int max_frame_length_ms) {
// Ensure that |SetReceiverFrameLengthRange| is called before
// |EnableAudioNetworkAdaptor|, otherwise we need to recreate
// |audio_network_adaptor_|, which is not a needed use case.
RTC_DCHECK(!audio_network_adaptor_);
FindSupportedFrameLengths(min_frame_length_ms, max_frame_length_ms,
&config_.supported_frame_lengths_ms);
}
AudioEncoder::EncodedInfo AudioEncoderOpus::EncodeImpl(
uint32_t rtp_timestamp,
rtc::ArrayView<const int16_t> audio,
rtc::Buffer* encoded) {
MaybeUpdateUplinkBandwidth();
if (input_buffer_.empty())
first_timestamp_in_buffer_ = rtp_timestamp;
input_buffer_.insert(input_buffer_.end(), audio.cbegin(), audio.cend());
if (input_buffer_.size() <
(Num10msFramesPerPacket() * SamplesPer10msFrame())) {
return EncodedInfo();
}
RTC_CHECK_EQ(input_buffer_.size(),
Num10msFramesPerPacket() * SamplesPer10msFrame());
const size_t max_encoded_bytes = SufficientOutputBufferSize();
EncodedInfo info;
info.encoded_bytes =
encoded->AppendData(
max_encoded_bytes, [&] (rtc::ArrayView<uint8_t> encoded) {
int status = WebRtcOpus_Encode(
inst_, &input_buffer_[0],
rtc::CheckedDivExact(input_buffer_.size(),
config_.num_channels),
rtc::saturated_cast<int16_t>(max_encoded_bytes),
encoded.data());
RTC_CHECK_GE(status, 0); // Fails only if fed invalid data.
return static_cast<size_t>(status);
});
input_buffer_.clear();
// Will use new packet size for next encoding.
config_.frame_size_ms = next_frame_length_ms_;
info.encoded_timestamp = first_timestamp_in_buffer_;
info.payload_type = payload_type_;
info.send_even_if_empty = true; // Allows Opus to send empty packets.
info.speech = (info.encoded_bytes > 0);
info.encoder_type = CodecType::kOpus;
return info;
}
size_t AudioEncoderOpus::Num10msFramesPerPacket() const {
return static_cast<size_t>(rtc::CheckedDivExact(config_.frame_size_ms, 10));
}
size_t AudioEncoderOpus::SamplesPer10msFrame() const {
return rtc::CheckedDivExact(kSampleRateHz, 100) * config_.num_channels;
}
size_t AudioEncoderOpus::SufficientOutputBufferSize() const {
// Calculate the number of bytes we expect the encoder to produce,
// then multiply by two to give a wide margin for error.
const size_t bytes_per_millisecond =
static_cast<size_t>(GetBitrateBps(config_) / (1000 * 8) + 1);
const size_t approx_encoded_bytes =
Num10msFramesPerPacket() * 10 * bytes_per_millisecond;
return 2 * approx_encoded_bytes;
}
// If the given config is OK, recreate the Opus encoder instance with those
// settings, save the config, and return true. Otherwise, do nothing and return
// false.
bool AudioEncoderOpus::RecreateEncoderInstance(
const AudioEncoderOpusConfig& config) {
if (!config.IsOk())
return false;
config_ = config;
if (inst_)
RTC_CHECK_EQ(0, WebRtcOpus_EncoderFree(inst_));
input_buffer_.clear();
input_buffer_.reserve(Num10msFramesPerPacket() * SamplesPer10msFrame());
RTC_CHECK_EQ(0, WebRtcOpus_EncoderCreate(
&inst_, config.num_channels,
config.application ==
AudioEncoderOpusConfig::ApplicationMode::kVoip
? 0
: 1));
RTC_CHECK_EQ(0, WebRtcOpus_SetBitRate(inst_, GetBitrateBps(config)));
if (config.fec_enabled) {
RTC_CHECK_EQ(0, WebRtcOpus_EnableFec(inst_));
} else {
RTC_CHECK_EQ(0, WebRtcOpus_DisableFec(inst_));
}
RTC_CHECK_EQ(
0, WebRtcOpus_SetMaxPlaybackRate(inst_, config.max_playback_rate_hz));
// Use the default complexity if the start bitrate is within the hysteresis
// window.
complexity_ = GetNewComplexity(config).value_or(config.complexity);
RTC_CHECK_EQ(0, WebRtcOpus_SetComplexity(inst_, complexity_));
if (config.dtx_enabled) {
RTC_CHECK_EQ(0, WebRtcOpus_EnableDtx(inst_));
} else {
RTC_CHECK_EQ(0, WebRtcOpus_DisableDtx(inst_));
}
RTC_CHECK_EQ(0,
WebRtcOpus_SetPacketLossRate(
inst_, static_cast<int32_t>(packet_loss_rate_ * 100 + .5)));
if (config.cbr_enabled) {
RTC_CHECK_EQ(0, WebRtcOpus_EnableCbr(inst_));
} else {
RTC_CHECK_EQ(0, WebRtcOpus_DisableCbr(inst_));
}
num_channels_to_encode_ = NumChannels();
next_frame_length_ms_ = config_.frame_size_ms;
return true;
}
void AudioEncoderOpus::SetFrameLength(int frame_length_ms) {
next_frame_length_ms_ = frame_length_ms;
}
void AudioEncoderOpus::SetNumChannelsToEncode(size_t num_channels_to_encode) {
RTC_DCHECK_GT(num_channels_to_encode, 0);
RTC_DCHECK_LE(num_channels_to_encode, config_.num_channels);
if (num_channels_to_encode_ == num_channels_to_encode)
return;
RTC_CHECK_EQ(0, WebRtcOpus_SetForceChannels(inst_, num_channels_to_encode));
num_channels_to_encode_ = num_channels_to_encode;
}
void AudioEncoderOpus::SetProjectedPacketLossRate(float fraction) {
float opt_loss_rate = OptimizePacketLossRate(fraction, packet_loss_rate_);
if (packet_loss_rate_ != opt_loss_rate) {
packet_loss_rate_ = opt_loss_rate;
RTC_CHECK_EQ(
0, WebRtcOpus_SetPacketLossRate(
inst_, static_cast<int32_t>(packet_loss_rate_ * 100 + .5)));
}
}
void AudioEncoderOpus::SetTargetBitrate(int bits_per_second) {
config_.bitrate_bps = rtc::Optional<int>(rtc::SafeClamp<int>(
bits_per_second, AudioEncoderOpusConfig::kMinBitrateBps,
AudioEncoderOpusConfig::kMaxBitrateBps));
RTC_DCHECK(config_.IsOk());
RTC_CHECK_EQ(0, WebRtcOpus_SetBitRate(inst_, GetBitrateBps(config_)));
const auto new_complexity = GetNewComplexity(config_);
if (new_complexity && complexity_ != *new_complexity) {
complexity_ = *new_complexity;
RTC_CHECK_EQ(0, WebRtcOpus_SetComplexity(inst_, complexity_));
}
}
void AudioEncoderOpus::ApplyAudioNetworkAdaptor() {
auto config = audio_network_adaptor_->GetEncoderRuntimeConfig();
if (config.bitrate_bps)
SetTargetBitrate(*config.bitrate_bps);
if (config.frame_length_ms)
SetFrameLength(*config.frame_length_ms);
if (config.enable_fec)
SetFec(*config.enable_fec);
if (config.uplink_packet_loss_fraction)
SetProjectedPacketLossRate(*config.uplink_packet_loss_fraction);
if (config.enable_dtx)
SetDtx(*config.enable_dtx);
if (config.num_channels)
SetNumChannelsToEncode(*config.num_channels);
}
std::unique_ptr<AudioNetworkAdaptor>
AudioEncoderOpus::DefaultAudioNetworkAdaptorCreator(
const ProtoString& config_string,
RtcEventLog* event_log) const {
AudioNetworkAdaptorImpl::Config config;
config.event_log = event_log;
return std::unique_ptr<AudioNetworkAdaptor>(new AudioNetworkAdaptorImpl(
config, ControllerManagerImpl::Create(
config_string, NumChannels(), supported_frame_lengths_ms(),
AudioEncoderOpusConfig::kMinBitrateBps,
num_channels_to_encode_, next_frame_length_ms_,
GetTargetBitrate(), config_.fec_enabled, GetDtx())));
}
void AudioEncoderOpus::MaybeUpdateUplinkBandwidth() {
if (audio_network_adaptor_) {
int64_t now_ms = rtc::TimeMillis();
if (!bitrate_smoother_last_update_time_ ||
now_ms - *bitrate_smoother_last_update_time_ >=
config_.uplink_bandwidth_update_interval_ms) {
rtc::Optional<float> smoothed_bitrate = bitrate_smoother_->GetAverage();
if (smoothed_bitrate)
audio_network_adaptor_->SetUplinkBandwidth(*smoothed_bitrate);
bitrate_smoother_last_update_time_ = rtc::Optional<int64_t>(now_ms);
}
}
}
ANAStats AudioEncoderOpus::GetANAStats() const {
if (audio_network_adaptor_) {
return audio_network_adaptor_->GetStats();
}
return ANAStats();
}
} // namespace webrtc

View File

@ -0,0 +1,182 @@
/*
* 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 WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_AUDIO_ENCODER_OPUS_H_
#define WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_AUDIO_ENCODER_OPUS_H_
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "webrtc/api/audio_codecs/audio_encoder.h"
#include "webrtc/api/audio_codecs/audio_format.h"
#include "webrtc/api/audio_codecs/opus/audio_encoder_opus_config.h"
#include "webrtc/api/optional.h"
#include "webrtc/common_audio/smoothing_filter.h"
#include "webrtc/modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h"
#include "webrtc/modules/audio_coding/codecs/opus/opus_interface.h"
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/rtc_base/protobuf_utils.h"
namespace webrtc {
class RtcEventLog;
struct CodecInst;
class AudioEncoderOpus final : public AudioEncoder {
public:
static void AppendSupportedEncoders(std::vector<AudioCodecSpec>* specs);
static AudioCodecInfo QueryAudioEncoder(const AudioEncoderOpusConfig& config);
static std::unique_ptr<AudioEncoder> MakeAudioEncoder(
const AudioEncoderOpusConfig&,
int payload_type);
// NOTE: This alias will soon go away. See
// https://bugs.chromium.org/p/webrtc/issues/detail?id=7847
using Config = AudioEncoderOpusConfig;
// NOTE: This function will soon go away. See
// https://bugs.chromium.org/p/webrtc/issues/detail?id=7847
static Config CreateConfig(int payload_type, const SdpAudioFormat& format);
static AudioEncoderOpusConfig CreateConfig(const CodecInst& codec_inst);
static rtc::Optional<AudioEncoderOpusConfig> SdpToConfig(
const SdpAudioFormat& format);
// Returns empty if the current bitrate falls within the hysteresis window,
// defined by complexity_threshold_bps +/- complexity_threshold_window_bps.
// Otherwise, returns the current complexity depending on whether the
// current bitrate is above or below complexity_threshold_bps.
static rtc::Optional<int> GetNewComplexity(
const AudioEncoderOpusConfig& config);
using AudioNetworkAdaptorCreator =
std::function<std::unique_ptr<AudioNetworkAdaptor>(const std::string&,
RtcEventLog*)>;
// NOTE: This constructor will soon go away. See
// https://bugs.chromium.org/p/webrtc/issues/detail?id=7847
AudioEncoderOpus(const AudioEncoderOpusConfig& config);
AudioEncoderOpus(const AudioEncoderOpusConfig& config, int payload_type);
// Dependency injection for testing.
AudioEncoderOpus(
const AudioEncoderOpusConfig& config,
int payload_type,
const AudioNetworkAdaptorCreator& audio_network_adaptor_creator,
std::unique_ptr<SmoothingFilter> bitrate_smoother);
explicit AudioEncoderOpus(const CodecInst& codec_inst);
AudioEncoderOpus(int payload_type, const SdpAudioFormat& format);
~AudioEncoderOpus() override;
// Static interface for use by BuiltinAudioEncoderFactory.
static constexpr const char* GetPayloadName() { return "opus"; }
static rtc::Optional<AudioCodecInfo> QueryAudioEncoder(
const SdpAudioFormat& format);
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;
bool SetFec(bool enable) override;
// Set Opus DTX. Once enabled, Opus stops transmission, when it detects
// voice being inactive. During that, it still sends 2 packets (one for
// content, one for signaling) about every 400 ms.
bool SetDtx(bool enable) override;
bool GetDtx() const override;
bool SetApplication(Application application) override;
void SetMaxPlaybackRate(int frequency_hz) override;
bool EnableAudioNetworkAdaptor(const std::string& config_string,
RtcEventLog* event_log) override;
void DisableAudioNetworkAdaptor() override;
void OnReceivedUplinkPacketLossFraction(
float uplink_packet_loss_fraction) override;
void OnReceivedUplinkRecoverablePacketLossFraction(
float uplink_recoverable_packet_loss_fraction) override;
void OnReceivedUplinkBandwidth(
int target_audio_bitrate_bps,
rtc::Optional<int64_t> bwe_period_ms) override;
void OnReceivedRtt(int rtt_ms) override;
void OnReceivedOverhead(size_t overhead_bytes_per_packet) override;
void SetReceiverFrameLengthRange(int min_frame_length_ms,
int max_frame_length_ms) override;
ANAStats GetANAStats() const override;
rtc::ArrayView<const int> supported_frame_lengths_ms() const {
return config_.supported_frame_lengths_ms;
}
// Getters for testing.
float packet_loss_rate() const { return packet_loss_rate_; }
AudioEncoderOpusConfig::ApplicationMode application() const {
return config_.application;
}
bool fec_enabled() const { return config_.fec_enabled; }
size_t num_channels_to_encode() const { return num_channels_to_encode_; }
int next_frame_length_ms() const { return next_frame_length_ms_; }
protected:
EncodedInfo EncodeImpl(uint32_t rtp_timestamp,
rtc::ArrayView<const int16_t> audio,
rtc::Buffer* encoded) override;
private:
class PacketLossFractionSmoother;
size_t Num10msFramesPerPacket() const;
size_t SamplesPer10msFrame() const;
size_t SufficientOutputBufferSize() const;
bool RecreateEncoderInstance(const AudioEncoderOpusConfig& config);
void SetFrameLength(int frame_length_ms);
void SetNumChannelsToEncode(size_t num_channels_to_encode);
void SetProjectedPacketLossRate(float fraction);
// TODO(minyue): remove "override" when we can deprecate
// |AudioEncoder::SetTargetBitrate|.
void SetTargetBitrate(int target_bps) override;
void ApplyAudioNetworkAdaptor();
std::unique_ptr<AudioNetworkAdaptor> DefaultAudioNetworkAdaptorCreator(
const ProtoString& config_string,
RtcEventLog* event_log) const;
void MaybeUpdateUplinkBandwidth();
AudioEncoderOpusConfig config_;
const int payload_type_;
const bool send_side_bwe_with_overhead_;
float packet_loss_rate_;
std::vector<int16_t> input_buffer_;
OpusEncInst* inst_;
uint32_t first_timestamp_in_buffer_;
size_t num_channels_to_encode_;
int next_frame_length_ms_;
int complexity_;
std::unique_ptr<PacketLossFractionSmoother> packet_loss_fraction_smoother_;
const AudioNetworkAdaptorCreator audio_network_adaptor_creator_;
std::unique_ptr<AudioNetworkAdaptor> audio_network_adaptor_;
rtc::Optional<size_t> overhead_bytes_per_packet_;
const std::unique_ptr<SmoothingFilter> bitrate_smoother_;
rtc::Optional<int64_t> bitrate_smoother_last_update_time_;
RTC_DISALLOW_COPY_AND_ASSIGN(AudioEncoderOpus);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_AUDIO_ENCODER_OPUS_H_

View File

@ -0,0 +1,763 @@
/*
* 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 <array>
#include <memory>
#include <utility>
#include "webrtc/common_audio/mocks/mock_smoothing_filter.h"
#include "webrtc/common_types.h"
#include "webrtc/modules/audio_coding/audio_network_adaptor/mock/mock_audio_network_adaptor.h"
#include "webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.h"
#include "webrtc/modules/audio_coding/neteq/tools/audio_loop.h"
#include "webrtc/rtc_base/checks.h"
#include "webrtc/rtc_base/fakeclock.h"
#include "webrtc/test/field_trial.h"
#include "webrtc/test/gmock.h"
#include "webrtc/test/gtest.h"
#include "webrtc/test/testsupport/fileutils.h"
namespace webrtc {
using ::testing::NiceMock;
using ::testing::Return;
namespace {
const CodecInst kDefaultOpusSettings = {105, "opus", 48000, 960, 1, 32000};
constexpr int64_t kInitialTimeUs = 12345678;
AudioEncoderOpusConfig CreateConfig(const CodecInst& codec_inst) {
AudioEncoderOpusConfig config;
config.frame_size_ms = rtc::CheckedDivExact(codec_inst.pacsize, 48);
config.num_channels = codec_inst.channels;
config.bitrate_bps = rtc::Optional<int>(codec_inst.rate);
config.application = config.num_channels == 1
? AudioEncoderOpusConfig::ApplicationMode::kVoip
: AudioEncoderOpusConfig::ApplicationMode::kAudio;
config.supported_frame_lengths_ms.push_back(config.frame_size_ms);
return config;
}
AudioEncoderOpusConfig CreateConfigWithParameters(
const SdpAudioFormat::Parameters& params) {
const SdpAudioFormat format("opus", 48000, 2, params);
return *AudioEncoderOpus::SdpToConfig(format);
}
struct AudioEncoderOpusStates {
std::shared_ptr<MockAudioNetworkAdaptor*> mock_audio_network_adaptor;
MockSmoothingFilter* mock_bitrate_smoother;
std::unique_ptr<AudioEncoderOpus> encoder;
std::unique_ptr<rtc::ScopedFakeClock> fake_clock;
AudioEncoderOpusConfig config;
};
AudioEncoderOpusStates CreateCodec(size_t num_channels) {
AudioEncoderOpusStates states;
states.mock_audio_network_adaptor =
std::make_shared<MockAudioNetworkAdaptor*>(nullptr);
states.fake_clock.reset(new rtc::ScopedFakeClock());
states.fake_clock->SetTimeMicros(kInitialTimeUs);
std::weak_ptr<MockAudioNetworkAdaptor*> mock_ptr(
states.mock_audio_network_adaptor);
AudioEncoderOpus::AudioNetworkAdaptorCreator creator =
[mock_ptr](const std::string&, RtcEventLog* event_log) {
std::unique_ptr<MockAudioNetworkAdaptor> adaptor(
new NiceMock<MockAudioNetworkAdaptor>());
EXPECT_CALL(*adaptor, Die());
if (auto sp = mock_ptr.lock()) {
*sp = adaptor.get();
} else {
RTC_NOTREACHED();
}
return adaptor;
};
CodecInst codec_inst = kDefaultOpusSettings;
codec_inst.channels = num_channels;
states.config = CreateConfig(codec_inst);
std::unique_ptr<MockSmoothingFilter> bitrate_smoother(
new MockSmoothingFilter());
states.mock_bitrate_smoother = bitrate_smoother.get();
states.encoder.reset(new AudioEncoderOpus(states.config, codec_inst.pltype,
std::move(creator),
std::move(bitrate_smoother)));
return states;
}
AudioEncoderRuntimeConfig CreateEncoderRuntimeConfig() {
constexpr int kBitrate = 40000;
constexpr int kFrameLength = 60;
constexpr bool kEnableFec = true;
constexpr bool kEnableDtx = false;
constexpr size_t kNumChannels = 1;
constexpr float kPacketLossFraction = 0.1f;
AudioEncoderRuntimeConfig config;
config.bitrate_bps = rtc::Optional<int>(kBitrate);
config.frame_length_ms = rtc::Optional<int>(kFrameLength);
config.enable_fec = rtc::Optional<bool>(kEnableFec);
config.enable_dtx = rtc::Optional<bool>(kEnableDtx);
config.num_channels = rtc::Optional<size_t>(kNumChannels);
config.uplink_packet_loss_fraction =
rtc::Optional<float>(kPacketLossFraction);
return config;
}
void CheckEncoderRuntimeConfig(const AudioEncoderOpus* encoder,
const AudioEncoderRuntimeConfig& config) {
EXPECT_EQ(*config.bitrate_bps, encoder->GetTargetBitrate());
EXPECT_EQ(*config.frame_length_ms, encoder->next_frame_length_ms());
EXPECT_EQ(*config.enable_fec, encoder->fec_enabled());
EXPECT_EQ(*config.enable_dtx, encoder->GetDtx());
EXPECT_EQ(*config.num_channels, encoder->num_channels_to_encode());
}
// Create 10ms audio data blocks for a total packet size of "packet_size_ms".
std::unique_ptr<test::AudioLoop> Create10msAudioBlocks(
const std::unique_ptr<AudioEncoderOpus>& encoder,
int packet_size_ms) {
const std::string file_name =
test::ResourcePath("audio_coding/testfile32kHz", "pcm");
std::unique_ptr<test::AudioLoop> speech_data(new test::AudioLoop());
int audio_samples_per_ms =
rtc::CheckedDivExact(encoder->SampleRateHz(), 1000);
if (!speech_data->Init(
file_name,
packet_size_ms * audio_samples_per_ms *
encoder->num_channels_to_encode(),
10 * audio_samples_per_ms * encoder->num_channels_to_encode()))
return nullptr;
return speech_data;
}
} // namespace
TEST(AudioEncoderOpusTest, DefaultApplicationModeMono) {
auto states = CreateCodec(1);
EXPECT_EQ(AudioEncoderOpusConfig::ApplicationMode::kVoip,
states.encoder->application());
}
TEST(AudioEncoderOpusTest, DefaultApplicationModeStereo) {
auto states = CreateCodec(2);
EXPECT_EQ(AudioEncoderOpusConfig::ApplicationMode::kAudio,
states.encoder->application());
}
TEST(AudioEncoderOpusTest, ChangeApplicationMode) {
auto states = CreateCodec(2);
EXPECT_TRUE(
states.encoder->SetApplication(AudioEncoder::Application::kSpeech));
EXPECT_EQ(AudioEncoderOpusConfig::ApplicationMode::kVoip,
states.encoder->application());
}
TEST(AudioEncoderOpusTest, ResetWontChangeApplicationMode) {
auto states = CreateCodec(2);
// Trigger a reset.
states.encoder->Reset();
// Verify that the mode is still kAudio.
EXPECT_EQ(AudioEncoderOpusConfig::ApplicationMode::kAudio,
states.encoder->application());
// Now change to kVoip.
EXPECT_TRUE(
states.encoder->SetApplication(AudioEncoder::Application::kSpeech));
EXPECT_EQ(AudioEncoderOpusConfig::ApplicationMode::kVoip,
states.encoder->application());
// Trigger a reset again.
states.encoder->Reset();
// Verify that the mode is still kVoip.
EXPECT_EQ(AudioEncoderOpusConfig::ApplicationMode::kVoip,
states.encoder->application());
}
TEST(AudioEncoderOpusTest, ToggleDtx) {
auto states = CreateCodec(2);
// Enable DTX
EXPECT_TRUE(states.encoder->SetDtx(true));
EXPECT_TRUE(states.encoder->GetDtx());
// Turn off DTX.
EXPECT_TRUE(states.encoder->SetDtx(false));
EXPECT_FALSE(states.encoder->GetDtx());
}
TEST(AudioEncoderOpusTest,
OnReceivedUplinkBandwidthWithoutAudioNetworkAdaptor) {
auto states = CreateCodec(1);
// Constants are replicated from audio_states.encoderopus.cc.
const int kMinBitrateBps = 6000;
const int kMaxBitrateBps = 510000;
// Set a too low bitrate.
states.encoder->OnReceivedUplinkBandwidth(kMinBitrateBps - 1,
rtc::Optional<int64_t>());
EXPECT_EQ(kMinBitrateBps, states.encoder->GetTargetBitrate());
// Set a too high bitrate.
states.encoder->OnReceivedUplinkBandwidth(kMaxBitrateBps + 1,
rtc::Optional<int64_t>());
EXPECT_EQ(kMaxBitrateBps, states.encoder->GetTargetBitrate());
// Set the minimum rate.
states.encoder->OnReceivedUplinkBandwidth(kMinBitrateBps,
rtc::Optional<int64_t>());
EXPECT_EQ(kMinBitrateBps, states.encoder->GetTargetBitrate());
// Set the maximum rate.
states.encoder->OnReceivedUplinkBandwidth(kMaxBitrateBps,
rtc::Optional<int64_t>());
EXPECT_EQ(kMaxBitrateBps, states.encoder->GetTargetBitrate());
// Set rates from kMaxBitrateBps up to 32000 bps.
for (int rate = kMinBitrateBps; rate <= 32000; rate += 1000) {
states.encoder->OnReceivedUplinkBandwidth(rate, rtc::Optional<int64_t>());
EXPECT_EQ(rate, states.encoder->GetTargetBitrate());
}
}
namespace {
// Returns a vector with the n evenly-spaced numbers a, a + (b - a)/(n - 1),
// ..., b.
std::vector<float> IntervalSteps(float a, float b, size_t n) {
RTC_DCHECK_GT(n, 1u);
const float step = (b - a) / (n - 1);
std::vector<float> points;
points.push_back(a);
for (size_t i = 1; i < n - 1; ++i)
points.push_back(a + i * step);
points.push_back(b);
return points;
}
// Sets the packet loss rate to each number in the vector in turn, and verifies
// that the loss rate as reported by the encoder is |expected_return| for all
// of them.
void TestSetPacketLossRate(AudioEncoderOpusStates* states,
const std::vector<float>& losses,
float expected_return) {
// |kSampleIntervalMs| is chosen to ease the calculation since
// 0.9999 ^ 184198 = 1e-8. Which minimizes the effect of
// PacketLossFractionSmoother used in AudioEncoderOpus.
constexpr int64_t kSampleIntervalMs = 184198;
for (float loss : losses) {
states->encoder->OnReceivedUplinkPacketLossFraction(loss);
states->fake_clock->AdvanceTime(
rtc::TimeDelta::FromMilliseconds(kSampleIntervalMs));
EXPECT_FLOAT_EQ(expected_return, states->encoder->packet_loss_rate());
}
}
} // namespace
TEST(AudioEncoderOpusTest, PacketLossRateOptimized) {
auto states = CreateCodec(1);
auto I = [](float a, float b) { return IntervalSteps(a, b, 10); };
constexpr float eps = 1e-8f;
// Note that the order of the following calls is critical.
// clang-format off
TestSetPacketLossRate(&states, I(0.00f , 0.01f - eps), 0.00f);
TestSetPacketLossRate(&states, I(0.01f + eps, 0.06f - eps), 0.01f);
TestSetPacketLossRate(&states, I(0.06f + eps, 0.11f - eps), 0.05f);
TestSetPacketLossRate(&states, I(0.11f + eps, 0.22f - eps), 0.10f);
TestSetPacketLossRate(&states, I(0.22f + eps, 1.00f ), 0.20f);
TestSetPacketLossRate(&states, I(1.00f , 0.18f + eps), 0.20f);
TestSetPacketLossRate(&states, I(0.18f - eps, 0.09f + eps), 0.10f);
TestSetPacketLossRate(&states, I(0.09f - eps, 0.04f + eps), 0.05f);
TestSetPacketLossRate(&states, I(0.04f - eps, 0.01f + eps), 0.01f);
TestSetPacketLossRate(&states, I(0.01f - eps, 0.00f ), 0.00f);
// clang-format on
}
TEST(AudioEncoderOpusTest, SetReceiverFrameLengthRange) {
auto states = CreateCodec(2);
// Before calling to |SetReceiverFrameLengthRange|,
// |supported_frame_lengths_ms| should contain only the frame length being
// used.
using ::testing::ElementsAre;
EXPECT_THAT(states.encoder->supported_frame_lengths_ms(),
ElementsAre(states.encoder->next_frame_length_ms()));
states.encoder->SetReceiverFrameLengthRange(0, 12345);
states.encoder->SetReceiverFrameLengthRange(21, 60);
EXPECT_THAT(states.encoder->supported_frame_lengths_ms(), ElementsAre(60));
states.encoder->SetReceiverFrameLengthRange(20, 59);
EXPECT_THAT(states.encoder->supported_frame_lengths_ms(), ElementsAre(20));
}
TEST(AudioEncoderOpusTest,
InvokeAudioNetworkAdaptorOnReceivedUplinkPacketLossFraction) {
auto states = CreateCodec(2);
states.encoder->EnableAudioNetworkAdaptor("", nullptr);
auto config = CreateEncoderRuntimeConfig();
EXPECT_CALL(**states.mock_audio_network_adaptor, GetEncoderRuntimeConfig())
.WillOnce(Return(config));
// Since using mock audio network adaptor, any packet loss fraction is fine.
constexpr float kUplinkPacketLoss = 0.1f;
EXPECT_CALL(**states.mock_audio_network_adaptor,
SetUplinkPacketLossFraction(kUplinkPacketLoss));
states.encoder->OnReceivedUplinkPacketLossFraction(kUplinkPacketLoss);
CheckEncoderRuntimeConfig(states.encoder.get(), config);
}
TEST(AudioEncoderOpusTest, InvokeAudioNetworkAdaptorOnReceivedUplinkBandwidth) {
auto states = CreateCodec(2);
states.encoder->EnableAudioNetworkAdaptor("", nullptr);
auto config = CreateEncoderRuntimeConfig();
EXPECT_CALL(**states.mock_audio_network_adaptor, GetEncoderRuntimeConfig())
.WillOnce(Return(config));
// Since using mock audio network adaptor, any target audio bitrate is fine.
constexpr int kTargetAudioBitrate = 30000;
constexpr int64_t kProbingIntervalMs = 3000;
EXPECT_CALL(**states.mock_audio_network_adaptor,
SetTargetAudioBitrate(kTargetAudioBitrate));
EXPECT_CALL(*states.mock_bitrate_smoother,
SetTimeConstantMs(kProbingIntervalMs * 4));
EXPECT_CALL(*states.mock_bitrate_smoother, AddSample(kTargetAudioBitrate));
states.encoder->OnReceivedUplinkBandwidth(
kTargetAudioBitrate, rtc::Optional<int64_t>(kProbingIntervalMs));
CheckEncoderRuntimeConfig(states.encoder.get(), config);
}
TEST(AudioEncoderOpusTest, InvokeAudioNetworkAdaptorOnReceivedRtt) {
auto states = CreateCodec(2);
states.encoder->EnableAudioNetworkAdaptor("", nullptr);
auto config = CreateEncoderRuntimeConfig();
EXPECT_CALL(**states.mock_audio_network_adaptor, GetEncoderRuntimeConfig())
.WillOnce(Return(config));
// Since using mock audio network adaptor, any rtt is fine.
constexpr int kRtt = 30;
EXPECT_CALL(**states.mock_audio_network_adaptor, SetRtt(kRtt));
states.encoder->OnReceivedRtt(kRtt);
CheckEncoderRuntimeConfig(states.encoder.get(), config);
}
TEST(AudioEncoderOpusTest, InvokeAudioNetworkAdaptorOnReceivedOverhead) {
auto states = CreateCodec(2);
states.encoder->EnableAudioNetworkAdaptor("", nullptr);
auto config = CreateEncoderRuntimeConfig();
EXPECT_CALL(**states.mock_audio_network_adaptor, GetEncoderRuntimeConfig())
.WillOnce(Return(config));
// Since using mock audio network adaptor, any overhead is fine.
constexpr size_t kOverhead = 64;
EXPECT_CALL(**states.mock_audio_network_adaptor, SetOverhead(kOverhead));
states.encoder->OnReceivedOverhead(kOverhead);
CheckEncoderRuntimeConfig(states.encoder.get(), config);
}
TEST(AudioEncoderOpusTest,
PacketLossFractionSmoothedOnSetUplinkPacketLossFraction) {
auto states = CreateCodec(2);
// The values are carefully chosen so that if no smoothing is made, the test
// will fail.
constexpr float kPacketLossFraction_1 = 0.02f;
constexpr float kPacketLossFraction_2 = 0.198f;
// |kSecondSampleTimeMs| is chosen to ease the calculation since
// 0.9999 ^ 6931 = 0.5.
constexpr int64_t kSecondSampleTimeMs = 6931;
// First time, no filtering.
states.encoder->OnReceivedUplinkPacketLossFraction(kPacketLossFraction_1);
EXPECT_FLOAT_EQ(0.01f, states.encoder->packet_loss_rate());
states.fake_clock->AdvanceTime(
rtc::TimeDelta::FromMilliseconds(kSecondSampleTimeMs));
states.encoder->OnReceivedUplinkPacketLossFraction(kPacketLossFraction_2);
// Now the output of packet loss fraction smoother should be
// (0.02 + 0.198) / 2 = 0.109, which reach the threshold for the optimized
// packet loss rate to increase to 0.05. If no smoothing has been made, the
// optimized packet loss rate should have been increase to 0.1.
EXPECT_FLOAT_EQ(0.05f, states.encoder->packet_loss_rate());
}
TEST(AudioEncoderOpusTest, DoNotInvokeSetTargetBitrateIfOverheadUnknown) {
test::ScopedFieldTrials override_field_trials(
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
auto states = CreateCodec(2);
states.encoder->OnReceivedUplinkBandwidth(kDefaultOpusSettings.rate * 2,
rtc::Optional<int64_t>());
// Since |OnReceivedOverhead| has not been called, the codec bitrate should
// not change.
EXPECT_EQ(kDefaultOpusSettings.rate, states.encoder->GetTargetBitrate());
}
TEST(AudioEncoderOpusTest, OverheadRemovedFromTargetAudioBitrate) {
test::ScopedFieldTrials override_field_trials(
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
auto states = CreateCodec(2);
constexpr size_t kOverheadBytesPerPacket = 64;
states.encoder->OnReceivedOverhead(kOverheadBytesPerPacket);
constexpr int kTargetBitrateBps = 40000;
states.encoder->OnReceivedUplinkBandwidth(kTargetBitrateBps,
rtc::Optional<int64_t>());
int packet_rate = rtc::CheckedDivExact(48000, kDefaultOpusSettings.pacsize);
EXPECT_EQ(kTargetBitrateBps -
8 * static_cast<int>(kOverheadBytesPerPacket) * packet_rate,
states.encoder->GetTargetBitrate());
}
TEST(AudioEncoderOpusTest, BitrateBounded) {
test::ScopedFieldTrials override_field_trials(
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
constexpr int kMinBitrateBps = 6000;
constexpr int kMaxBitrateBps = 510000;
auto states = CreateCodec(2);
constexpr size_t kOverheadBytesPerPacket = 64;
states.encoder->OnReceivedOverhead(kOverheadBytesPerPacket);
int packet_rate = rtc::CheckedDivExact(48000, kDefaultOpusSettings.pacsize);
// Set a target rate that is smaller than |kMinBitrateBps| when overhead is
// subtracted. The eventual codec rate should be bounded by |kMinBitrateBps|.
int target_bitrate =
kOverheadBytesPerPacket * 8 * packet_rate + kMinBitrateBps - 1;
states.encoder->OnReceivedUplinkBandwidth(target_bitrate,
rtc::Optional<int64_t>());
EXPECT_EQ(kMinBitrateBps, states.encoder->GetTargetBitrate());
// Set a target rate that is greater than |kMaxBitrateBps| when overhead is
// subtracted. The eventual codec rate should be bounded by |kMaxBitrateBps|.
target_bitrate =
kOverheadBytesPerPacket * 8 * packet_rate + kMaxBitrateBps + 1;
states.encoder->OnReceivedUplinkBandwidth(target_bitrate,
rtc::Optional<int64_t>());
EXPECT_EQ(kMaxBitrateBps, states.encoder->GetTargetBitrate());
}
// Verifies that the complexity adaptation in the config works as intended.
TEST(AudioEncoderOpusTest, ConfigComplexityAdaptation) {
AudioEncoderOpusConfig config;
config.low_rate_complexity = 8;
config.complexity = 6;
// Bitrate within hysteresis window. Expect empty output.
config.bitrate_bps = rtc::Optional<int>(12500);
EXPECT_EQ(rtc::Optional<int>(), AudioEncoderOpus::GetNewComplexity(config));
// Bitrate below hysteresis window. Expect higher complexity.
config.bitrate_bps = rtc::Optional<int>(10999);
EXPECT_EQ(rtc::Optional<int>(8), AudioEncoderOpus::GetNewComplexity(config));
// Bitrate within hysteresis window. Expect empty output.
config.bitrate_bps = rtc::Optional<int>(12500);
EXPECT_EQ(rtc::Optional<int>(), AudioEncoderOpus::GetNewComplexity(config));
// Bitrate above hysteresis window. Expect lower complexity.
config.bitrate_bps = rtc::Optional<int>(14001);
EXPECT_EQ(rtc::Optional<int>(6), AudioEncoderOpus::GetNewComplexity(config));
}
TEST(AudioEncoderOpusTest, EmptyConfigDoesNotAffectEncoderSettings) {
auto states = CreateCodec(2);
states.encoder->EnableAudioNetworkAdaptor("", nullptr);
auto config = CreateEncoderRuntimeConfig();
AudioEncoderRuntimeConfig empty_config;
EXPECT_CALL(**states.mock_audio_network_adaptor, GetEncoderRuntimeConfig())
.WillOnce(Return(config))
.WillOnce(Return(empty_config));
constexpr size_t kOverhead = 64;
EXPECT_CALL(**states.mock_audio_network_adaptor, SetOverhead(kOverhead))
.Times(2);
states.encoder->OnReceivedOverhead(kOverhead);
states.encoder->OnReceivedOverhead(kOverhead);
CheckEncoderRuntimeConfig(states.encoder.get(), config);
}
TEST(AudioEncoderOpusTest, UpdateUplinkBandwidthInAudioNetworkAdaptor) {
auto states = CreateCodec(2);
states.encoder->EnableAudioNetworkAdaptor("", nullptr);
std::array<int16_t, 480 * 2> audio;
audio.fill(0);
rtc::Buffer encoded;
EXPECT_CALL(*states.mock_bitrate_smoother, GetAverage())
.WillOnce(Return(rtc::Optional<float>(50000)));
EXPECT_CALL(**states.mock_audio_network_adaptor, SetUplinkBandwidth(50000));
states.encoder->Encode(
0, rtc::ArrayView<const int16_t>(audio.data(), audio.size()), &encoded);
// Repeat update uplink bandwidth tests.
for (int i = 0; i < 5; i++) {
// Don't update till it is time to update again.
states.fake_clock->AdvanceTime(rtc::TimeDelta::FromMilliseconds(
states.config.uplink_bandwidth_update_interval_ms - 1));
states.encoder->Encode(
0, rtc::ArrayView<const int16_t>(audio.data(), audio.size()), &encoded);
// Update when it is time to update.
EXPECT_CALL(*states.mock_bitrate_smoother, GetAverage())
.WillOnce(Return(rtc::Optional<float>(40000)));
EXPECT_CALL(**states.mock_audio_network_adaptor, SetUplinkBandwidth(40000));
states.fake_clock->AdvanceTime(rtc::TimeDelta::FromMilliseconds(1));
states.encoder->Encode(
0, rtc::ArrayView<const int16_t>(audio.data(), audio.size()), &encoded);
}
}
TEST(AudioEncoderOpusTest, EncodeAtMinBitrate) {
auto states = CreateCodec(1);
constexpr int kNumPacketsToEncode = 2;
auto audio_frames =
Create10msAudioBlocks(states.encoder, kNumPacketsToEncode * 20);
ASSERT_TRUE(audio_frames) << "Create10msAudioBlocks failed";
rtc::Buffer encoded;
uint32_t rtp_timestamp = 12345; // Just a number not important to this test.
states.encoder->OnReceivedUplinkBandwidth(0, rtc::Optional<int64_t>());
for (int packet_index = 0; packet_index < kNumPacketsToEncode;
packet_index++) {
// Make sure we are not encoding before we have enough data for
// a 20ms packet.
for (int index = 0; index < 1; index++) {
states.encoder->Encode(rtp_timestamp, audio_frames->GetNextBlock(),
&encoded);
EXPECT_EQ(0u, encoded.size());
}
// Should encode now.
states.encoder->Encode(rtp_timestamp, audio_frames->GetNextBlock(),
&encoded);
EXPECT_GT(encoded.size(), 0u);
encoded.Clear();
}
}
TEST(AudioEncoderOpusTest, TestConfigDefaults) {
const auto config_opt = AudioEncoderOpus::SdpToConfig({"opus", 48000, 2});
ASSERT_TRUE(config_opt);
EXPECT_EQ(48000, config_opt->max_playback_rate_hz);
EXPECT_EQ(1u, config_opt->num_channels);
EXPECT_FALSE(config_opt->fec_enabled);
EXPECT_FALSE(config_opt->dtx_enabled);
EXPECT_EQ(20, config_opt->frame_size_ms);
}
TEST(AudioEncoderOpusTest, TestConfigFromParams) {
const auto config1 = CreateConfigWithParameters({{"stereo", "0"}});
EXPECT_EQ(1U, config1.num_channels);
const auto config2 = CreateConfigWithParameters({{"stereo", "1"}});
EXPECT_EQ(2U, config2.num_channels);
const auto config3 = CreateConfigWithParameters({{"useinbandfec", "0"}});
EXPECT_FALSE(config3.fec_enabled);
const auto config4 = CreateConfigWithParameters({{"useinbandfec", "1"}});
EXPECT_TRUE(config4.fec_enabled);
const auto config5 = CreateConfigWithParameters({{"usedtx", "0"}});
EXPECT_FALSE(config5.dtx_enabled);
const auto config6 = CreateConfigWithParameters({{"usedtx", "1"}});
EXPECT_TRUE(config6.dtx_enabled);
const auto config7 = CreateConfigWithParameters({{"cbr", "0"}});
EXPECT_FALSE(config7.cbr_enabled);
const auto config8 = CreateConfigWithParameters({{"cbr", "1"}});
EXPECT_TRUE(config8.cbr_enabled);
const auto config9 =
CreateConfigWithParameters({{"maxplaybackrate", "12345"}});
EXPECT_EQ(12345, config9.max_playback_rate_hz);
const auto config10 =
CreateConfigWithParameters({{"maxaveragebitrate", "96000"}});
EXPECT_EQ(96000, config10.bitrate_bps);
const auto config11 = CreateConfigWithParameters({{"maxptime", "40"}});
for (int frame_length : config11.supported_frame_lengths_ms) {
EXPECT_LE(frame_length, 40);
}
const auto config12 = CreateConfigWithParameters({{"minptime", "40"}});
for (int frame_length : config12.supported_frame_lengths_ms) {
EXPECT_GE(frame_length, 40);
}
const auto config13 = CreateConfigWithParameters({{"ptime", "40"}});
EXPECT_EQ(40, config13.frame_size_ms);
constexpr int kMinSupportedFrameLength = 10;
constexpr int kMaxSupportedFrameLength =
WEBRTC_OPUS_SUPPORT_120MS_PTIME ? 120 : 60;
const auto config14 = CreateConfigWithParameters({{"ptime", "1"}});
EXPECT_EQ(kMinSupportedFrameLength, config14.frame_size_ms);
const auto config15 = CreateConfigWithParameters({{"ptime", "2000"}});
EXPECT_EQ(kMaxSupportedFrameLength, config15.frame_size_ms);
}
TEST(AudioEncoderOpusTest, TestConfigFromInvalidParams) {
const webrtc::SdpAudioFormat format("opus", 48000, 2);
const auto default_config = *AudioEncoderOpus::SdpToConfig(format);
#if WEBRTC_OPUS_SUPPORT_120MS_PTIME
const std::vector<int> default_supported_frame_lengths_ms({20, 60, 120});
#else
const std::vector<int> default_supported_frame_lengths_ms({20, 60});
#endif
AudioEncoderOpusConfig config;
config = CreateConfigWithParameters({{"stereo", "invalid"}});
EXPECT_EQ(default_config.num_channels, config.num_channels);
config = CreateConfigWithParameters({{"useinbandfec", "invalid"}});
EXPECT_EQ(default_config.fec_enabled, config.fec_enabled);
config = CreateConfigWithParameters({{"usedtx", "invalid"}});
EXPECT_EQ(default_config.dtx_enabled, config.dtx_enabled);
config = CreateConfigWithParameters({{"cbr", "invalid"}});
EXPECT_EQ(default_config.dtx_enabled, config.dtx_enabled);
config = CreateConfigWithParameters({{"maxplaybackrate", "0"}});
EXPECT_EQ(default_config.max_playback_rate_hz, config.max_playback_rate_hz);
config = CreateConfigWithParameters({{"maxplaybackrate", "-23"}});
EXPECT_EQ(default_config.max_playback_rate_hz, config.max_playback_rate_hz);
config = CreateConfigWithParameters({{"maxplaybackrate", "not a number!"}});
EXPECT_EQ(default_config.max_playback_rate_hz, config.max_playback_rate_hz);
config = CreateConfigWithParameters({{"maxaveragebitrate", "0"}});
EXPECT_EQ(6000, config.bitrate_bps);
config = CreateConfigWithParameters({{"maxaveragebitrate", "-1000"}});
EXPECT_EQ(6000, config.bitrate_bps);
config = CreateConfigWithParameters({{"maxaveragebitrate", "1024000"}});
EXPECT_EQ(510000, config.bitrate_bps);
config = CreateConfigWithParameters({{"maxaveragebitrate", "not a number!"}});
EXPECT_EQ(default_config.bitrate_bps, config.bitrate_bps);
config = CreateConfigWithParameters({{"maxptime", "invalid"}});
EXPECT_EQ(default_supported_frame_lengths_ms,
config.supported_frame_lengths_ms);
config = CreateConfigWithParameters({{"minptime", "invalid"}});
EXPECT_EQ(default_supported_frame_lengths_ms,
config.supported_frame_lengths_ms);
config = CreateConfigWithParameters({{"ptime", "invalid"}});
EXPECT_EQ(default_supported_frame_lengths_ms,
config.supported_frame_lengths_ms);
}
// Test that bitrate will be overridden by the "maxaveragebitrate" parameter.
// Also test that the "maxaveragebitrate" can't be set to values outside the
// range of 6000 and 510000
TEST(AudioEncoderOpusTest, SetSendCodecOpusMaxAverageBitrate) {
// Ignore if less than 6000.
const auto config1 = AudioEncoderOpus::SdpToConfig(
{"opus", 48000, 2, {{"maxaveragebitrate", "5999"}}});
EXPECT_EQ(6000, config1->bitrate_bps);
// Ignore if larger than 510000.
const auto config2 = AudioEncoderOpus::SdpToConfig(
{"opus", 48000, 2, {{"maxaveragebitrate", "510001"}}});
EXPECT_EQ(510000, config2->bitrate_bps);
const auto config3 = AudioEncoderOpus::SdpToConfig(
{"opus", 48000, 2, {{"maxaveragebitrate", "200000"}}});
EXPECT_EQ(200000, config3->bitrate_bps);
}
// Test maxplaybackrate <= 8000 triggers Opus narrow band mode.
TEST(AudioEncoderOpusTest, SetMaxPlaybackRateNb) {
auto config = CreateConfigWithParameters({{"maxplaybackrate", "8000"}});
EXPECT_EQ(8000, config.max_playback_rate_hz);
EXPECT_EQ(12000, config.bitrate_bps);
config = CreateConfigWithParameters({{"maxplaybackrate", "8000"},
{"stereo", "1"}});
EXPECT_EQ(8000, config.max_playback_rate_hz);
EXPECT_EQ(24000, config.bitrate_bps);
}
// Test 8000 < maxplaybackrate <= 12000 triggers Opus medium band mode.
TEST(AudioEncoderOpusTest, SetMaxPlaybackRateMb) {
auto config = CreateConfigWithParameters({{"maxplaybackrate", "8001"}});
EXPECT_EQ(8001, config.max_playback_rate_hz);
EXPECT_EQ(20000, config.bitrate_bps);
config = CreateConfigWithParameters({{"maxplaybackrate", "8001"},
{"stereo", "1"}});
EXPECT_EQ(8001, config.max_playback_rate_hz);
EXPECT_EQ(40000, config.bitrate_bps);
}
// Test 12000 < maxplaybackrate <= 16000 triggers Opus wide band mode.
TEST(AudioEncoderOpusTest, SetMaxPlaybackRateWb) {
auto config = CreateConfigWithParameters({{"maxplaybackrate", "12001"}});
EXPECT_EQ(12001, config.max_playback_rate_hz);
EXPECT_EQ(20000, config.bitrate_bps);
config = CreateConfigWithParameters({{"maxplaybackrate", "12001"},
{"stereo", "1"}});
EXPECT_EQ(12001, config.max_playback_rate_hz);
EXPECT_EQ(40000, config.bitrate_bps);
}
// Test 16000 < maxplaybackrate <= 24000 triggers Opus super wide band mode.
TEST(AudioEncoderOpusTest, SetMaxPlaybackRateSwb) {
auto config = CreateConfigWithParameters({{"maxplaybackrate", "16001"}});
EXPECT_EQ(16001, config.max_playback_rate_hz);
EXPECT_EQ(32000, config.bitrate_bps);
config = CreateConfigWithParameters({{"maxplaybackrate", "16001"},
{"stereo", "1"}});
EXPECT_EQ(16001, config.max_playback_rate_hz);
EXPECT_EQ(64000, config.bitrate_bps);
}
// Test 24000 < maxplaybackrate triggers Opus full band mode.
TEST(AudioEncoderOpusTest, SetMaxPlaybackRateFb) {
auto config = CreateConfigWithParameters({{"maxplaybackrate", "24001"}});
EXPECT_EQ(24001, config.max_playback_rate_hz);
EXPECT_EQ(32000, config.bitrate_bps);
config = CreateConfigWithParameters({{"maxplaybackrate", "24001"},
{"stereo", "1"}});
EXPECT_EQ(24001, config.max_playback_rate_hz);
EXPECT_EQ(64000, config.bitrate_bps);
}
} // namespace webrtc

View File

@ -0,0 +1,98 @@
/*
* 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 "webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.h"
#include "webrtc/modules/audio_coding/neteq/tools/audio_loop.h"
#include "webrtc/rtc_base/format_macros.h"
#include "webrtc/rtc_base/timeutils.h"
#include "webrtc/test/gtest.h"
#include "webrtc/test/testsupport/fileutils.h"
#include "webrtc/test/testsupport/perf_test.h"
namespace webrtc {
namespace {
int64_t RunComplexityTest(const AudioEncoderOpusConfig& config) {
// Create encoder.
constexpr int payload_type = 17;
AudioEncoderOpus encoder(config, payload_type);
// Open speech file.
const std::string kInputFileName =
webrtc::test::ResourcePath("audio_coding/speech_mono_32_48kHz", "pcm");
test::AudioLoop audio_loop;
constexpr int kSampleRateHz = 48000;
EXPECT_EQ(kSampleRateHz, encoder.SampleRateHz());
constexpr size_t kMaxLoopLengthSamples =
kSampleRateHz * 10; // 10 second loop.
constexpr size_t kInputBlockSizeSamples =
10 * kSampleRateHz / 1000; // 60 ms.
EXPECT_TRUE(audio_loop.Init(kInputFileName, kMaxLoopLengthSamples,
kInputBlockSizeSamples));
// Encode.
const int64_t start_time_ms = rtc::TimeMillis();
AudioEncoder::EncodedInfo info;
rtc::Buffer encoded(500);
uint32_t rtp_timestamp = 0u;
for (size_t i = 0; i < 10000; ++i) {
encoded.Clear();
info = encoder.Encode(rtp_timestamp, audio_loop.GetNextBlock(), &encoded);
rtp_timestamp += kInputBlockSizeSamples;
}
return rtc::TimeMillis() - start_time_ms;
}
} // namespace
// This test encodes an audio file using Opus twice with different bitrates
// (~11 kbps and 15.5 kbps). The runtime for each is measured, and the ratio
// between the two is calculated and tracked. This test explicitly sets the
// low_rate_complexity to 9. When running on desktop platforms, this is the same
// as the regular complexity, and the expectation is that the resulting ratio
// should be less than 100% (since the encoder runs faster at lower bitrates,
// given a fixed complexity setting). On the other hand, when running on
// mobiles, the regular complexity is 5, and we expect the resulting ratio to
// be higher, since we have explicitly asked for a higher complexity setting at
// the lower rate.
TEST(AudioEncoderOpusComplexityAdaptationTest, AdaptationOn) {
// Create config.
AudioEncoderOpusConfig config;
// The limit -- including the hysteresis window -- at which the complexity
// shuold be increased.
config.bitrate_bps = rtc::Optional<int>(11000 - 1);
config.low_rate_complexity = 9;
int64_t runtime_10999bps = RunComplexityTest(config);
config.bitrate_bps = rtc::Optional<int>(15500);
int64_t runtime_15500bps = RunComplexityTest(config);
test::PrintResult("opus_encoding_complexity_ratio", "", "adaptation_on",
100.0 * runtime_10999bps / runtime_15500bps, "percent",
true);
}
// This test is identical to the one above, but without the complexity
// adaptation enabled (neither on desktop, nor on mobile). The expectation is
// that the resulting ratio is less than 100% at all times.
TEST(AudioEncoderOpusComplexityAdaptationTest, AdaptationOff) {
// Create config.
AudioEncoderOpusConfig config;
// The limit -- including the hysteresis window -- at which the complexity
// shuold be increased (but not in this test since complexity adaptation is
// disabled).
config.bitrate_bps = rtc::Optional<int>(11000 - 1);
int64_t runtime_10999bps = RunComplexityTest(config);
config.bitrate_bps = rtc::Optional<int>(15500);
int64_t runtime_15500bps = RunComplexityTest(config);
test::PrintResult("opus_encoding_complexity_ratio", "", "adaptation_off",
100.0 * runtime_10999bps / runtime_15500bps, "percent",
true);
}
} // namespace webrtc

View File

@ -0,0 +1,242 @@
/*
* 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 <memory>
#include "webrtc/modules/audio_coding/codecs/opus/opus_interface.h"
#include "webrtc/rtc_base/format_macros.h"
#include "webrtc/test/gtest.h"
#include "webrtc/test/testsupport/fileutils.h"
using ::std::string;
using ::std::tr1::tuple;
using ::std::tr1::get;
using ::testing::TestWithParam;
namespace webrtc {
// Define coding parameter as <channels, bit_rate, filename, extension>.
typedef tuple<size_t, int, string, string> coding_param;
typedef struct mode mode;
struct mode {
bool fec;
uint8_t target_packet_loss_rate;
};
const int kOpusBlockDurationMs = 20;
const int kOpusSamplingKhz = 48;
class OpusFecTest : public TestWithParam<coding_param> {
protected:
OpusFecTest();
virtual void SetUp();
virtual void TearDown();
virtual void EncodeABlock();
virtual void DecodeABlock(bool lost_previous, bool lost_current);
int block_duration_ms_;
int sampling_khz_;
size_t block_length_sample_;
size_t channels_;
int bit_rate_;
size_t data_pointer_;
size_t loop_length_samples_;
size_t max_bytes_;
size_t encoded_bytes_;
WebRtcOpusEncInst* opus_encoder_;
WebRtcOpusDecInst* opus_decoder_;
string in_filename_;
std::unique_ptr<int16_t[]> in_data_;
std::unique_ptr<int16_t[]> out_data_;
std::unique_ptr<uint8_t[]> bit_stream_;
};
void OpusFecTest::SetUp() {
channels_ = get<0>(GetParam());
bit_rate_ = get<1>(GetParam());
printf("Coding %" PRIuS " channel signal at %d bps.\n", channels_, bit_rate_);
in_filename_ = test::ResourcePath(get<2>(GetParam()), get<3>(GetParam()));
FILE* fp = fopen(in_filename_.c_str(), "rb");
ASSERT_FALSE(fp == NULL);
// Obtain file size.
fseek(fp, 0, SEEK_END);
loop_length_samples_ = ftell(fp) / sizeof(int16_t);
rewind(fp);
// Allocate memory to contain the whole file.
in_data_.reset(new int16_t[loop_length_samples_ +
block_length_sample_ * channels_]);
// Copy the file into the buffer.
ASSERT_EQ(fread(&in_data_[0], sizeof(int16_t), loop_length_samples_, fp),
loop_length_samples_);
fclose(fp);
// The audio will be used in a looped manner. To ease the acquisition of an
// audio frame that crosses the end of the excerpt, we add an extra block
// length of samples to the end of the array, starting over again from the
// beginning of the array. Audio frames cross the end of the excerpt always
// appear as a continuum of memory.
memcpy(&in_data_[loop_length_samples_], &in_data_[0],
block_length_sample_ * channels_ * sizeof(int16_t));
// Maximum number of bytes in output bitstream.
max_bytes_ = block_length_sample_ * channels_ * sizeof(int16_t);
out_data_.reset(new int16_t[2 * block_length_sample_ * channels_]);
bit_stream_.reset(new uint8_t[max_bytes_]);
// If channels_ == 1, use Opus VOIP mode, otherwise, audio mode.
int app = channels_ == 1 ? 0 : 1;
// Create encoder memory.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_, channels_, app));
EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_, channels_));
// Set bitrate.
EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_, bit_rate_));
}
void OpusFecTest::TearDown() {
// Free memory.
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
}
OpusFecTest::OpusFecTest()
: block_duration_ms_(kOpusBlockDurationMs),
sampling_khz_(kOpusSamplingKhz),
block_length_sample_(
static_cast<size_t>(block_duration_ms_ * sampling_khz_)),
data_pointer_(0),
max_bytes_(0),
encoded_bytes_(0),
opus_encoder_(NULL),
opus_decoder_(NULL) {
}
void OpusFecTest::EncodeABlock() {
int value = WebRtcOpus_Encode(opus_encoder_,
&in_data_[data_pointer_],
block_length_sample_,
max_bytes_, &bit_stream_[0]);
EXPECT_GT(value, 0);
encoded_bytes_ = static_cast<size_t>(value);
}
void OpusFecTest::DecodeABlock(bool lost_previous, bool lost_current) {
int16_t audio_type;
int value_1 = 0, value_2 = 0;
if (lost_previous) {
// Decode previous frame.
if (!lost_current &&
WebRtcOpus_PacketHasFec(&bit_stream_[0], encoded_bytes_) == 1) {
value_1 = WebRtcOpus_DecodeFec(opus_decoder_, &bit_stream_[0],
encoded_bytes_, &out_data_[0],
&audio_type);
} else {
value_1 = WebRtcOpus_DecodePlc(opus_decoder_, &out_data_[0], 1);
}
EXPECT_EQ(static_cast<int>(block_length_sample_), value_1);
}
if (!lost_current) {
// Decode current frame.
value_2 = WebRtcOpus_Decode(opus_decoder_, &bit_stream_[0], encoded_bytes_,
&out_data_[value_1 * channels_], &audio_type);
EXPECT_EQ(static_cast<int>(block_length_sample_), value_2);
}
}
TEST_P(OpusFecTest, RandomPacketLossTest) {
const int kDurationMs = 200000;
int time_now_ms, fec_frames;
int actual_packet_loss_rate;
bool lost_current, lost_previous;
mode mode_set[3] = {{true, 0},
{false, 0},
{true, 50}};
lost_current = false;
for (int i = 0; i < 3; i++) {
if (mode_set[i].fec) {
EXPECT_EQ(0, WebRtcOpus_EnableFec(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_SetPacketLossRate(opus_encoder_,
mode_set[i].target_packet_loss_rate));
printf("FEC is ON, target at packet loss rate %d percent.\n",
mode_set[i].target_packet_loss_rate);
} else {
EXPECT_EQ(0, WebRtcOpus_DisableFec(opus_encoder_));
printf("FEC is OFF.\n");
}
// In this test, we let the target packet loss rate match the actual rate.
actual_packet_loss_rate = mode_set[i].target_packet_loss_rate;
// Run every mode a certain time.
time_now_ms = 0;
fec_frames = 0;
while (time_now_ms < kDurationMs) {
// Encode & decode.
EncodeABlock();
// Check if payload has FEC.
int fec = WebRtcOpus_PacketHasFec(&bit_stream_[0], encoded_bytes_);
// If FEC is disabled or the target packet loss rate is set to 0, there
// should be no FEC in the bit stream.
if (!mode_set[i].fec || mode_set[i].target_packet_loss_rate == 0) {
EXPECT_EQ(fec, 0);
} else if (fec == 1) {
fec_frames++;
}
lost_previous = lost_current;
lost_current = rand() < actual_packet_loss_rate * (RAND_MAX / 100);
DecodeABlock(lost_previous, lost_current);
time_now_ms += block_duration_ms_;
// |data_pointer_| is incremented and wrapped across
// |loop_length_samples_|.
data_pointer_ = (data_pointer_ + block_length_sample_ * channels_) %
loop_length_samples_;
}
if (mode_set[i].fec) {
printf("%.2f percent frames has FEC.\n",
static_cast<float>(fec_frames) * block_duration_ms_ / 2000);
}
}
}
const coding_param param_set[] =
{::std::tr1::make_tuple(1, 64000, string("audio_coding/testfile32kHz"),
string("pcm")),
::std::tr1::make_tuple(1, 32000, string("audio_coding/testfile32kHz"),
string("pcm")),
::std::tr1::make_tuple(2, 64000, string("audio_coding/teststereo32kHz"),
string("pcm"))};
// 64 kbps, stereo
INSTANTIATE_TEST_CASE_P(AllTest, OpusFecTest,
::testing::ValuesIn(param_set));
} // namespace webrtc

View File

@ -0,0 +1,36 @@
/*
* 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 WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_OPUS_INST_H_
#define WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_OPUS_INST_H_
#include <stddef.h>
#include "webrtc/rtc_base/ignore_wundef.h"
RTC_PUSH_IGNORING_WUNDEF()
#include "opus.h"
RTC_POP_IGNORING_WUNDEF()
struct WebRtcOpusEncInst {
OpusEncoder* encoder;
size_t channels;
int in_dtx_mode;
};
struct WebRtcOpusDecInst {
OpusDecoder* decoder;
int prev_decoded_samples;
size_t channels;
int in_dtx_mode;
};
#endif // WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_OPUS_INST_H_

View File

@ -0,0 +1,503 @@
/*
* 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 "webrtc/modules/audio_coding/codecs/opus/opus_interface.h"
#include "webrtc/rtc_base/checks.h"
#include "webrtc/modules/audio_coding/codecs/opus/opus_inst.h"
#include <stdlib.h>
#include <string.h>
enum {
#if WEBRTC_OPUS_SUPPORT_120MS_PTIME
/* Maximum supported frame size in WebRTC is 120 ms. */
kWebRtcOpusMaxEncodeFrameSizeMs = 120,
#else
/* Maximum supported frame size in WebRTC is 60 ms. */
kWebRtcOpusMaxEncodeFrameSizeMs = 60,
#endif
/* The format allows up to 120 ms frames. Since we don't control the other
* side, we must allow for packets of that size. NetEq is currently limited
* to 60 ms on the receive side. */
kWebRtcOpusMaxDecodeFrameSizeMs = 120,
/* Maximum sample count per channel is 48 kHz * maximum frame size in
* milliseconds. */
kWebRtcOpusMaxFrameSizePerChannel = 48 * kWebRtcOpusMaxDecodeFrameSizeMs,
/* Default frame size, 20 ms @ 48 kHz, in samples (for one channel). */
kWebRtcOpusDefaultFrameSize = 960,
};
int16_t WebRtcOpus_EncoderCreate(OpusEncInst** inst,
size_t channels,
int32_t application) {
int opus_app;
if (!inst)
return -1;
switch (application) {
case 0:
opus_app = OPUS_APPLICATION_VOIP;
break;
case 1:
opus_app = OPUS_APPLICATION_AUDIO;
break;
default:
return -1;
}
OpusEncInst* state = calloc(1, sizeof(OpusEncInst));
RTC_DCHECK(state);
int error;
state->encoder = opus_encoder_create(48000, (int)channels, opus_app,
&error);
if (error != OPUS_OK || !state->encoder) {
WebRtcOpus_EncoderFree(state);
return -1;
}
state->in_dtx_mode = 0;
state->channels = channels;
*inst = state;
return 0;
}
int16_t WebRtcOpus_EncoderFree(OpusEncInst* inst) {
if (inst) {
opus_encoder_destroy(inst->encoder);
free(inst);
return 0;
} else {
return -1;
}
}
int WebRtcOpus_Encode(OpusEncInst* inst,
const int16_t* audio_in,
size_t samples,
size_t length_encoded_buffer,
uint8_t* encoded) {
int res;
if (samples > 48 * kWebRtcOpusMaxEncodeFrameSizeMs) {
return -1;
}
res = opus_encode(inst->encoder,
(const opus_int16*)audio_in,
(int)samples,
encoded,
(opus_int32)length_encoded_buffer);
if (res <= 0) {
return -1;
}
if (res <= 2) {
// Indicates DTX since the packet has nothing but a header. In principle,
// there is no need to send this packet. However, we do transmit the first
// occurrence to let the decoder know that the encoder enters DTX mode.
if (inst->in_dtx_mode) {
return 0;
} else {
inst->in_dtx_mode = 1;
return res;
}
}
inst->in_dtx_mode = 0;
return res;
}
int16_t WebRtcOpus_SetBitRate(OpusEncInst* inst, int32_t rate) {
if (inst) {
return opus_encoder_ctl(inst->encoder, OPUS_SET_BITRATE(rate));
} else {
return -1;
}
}
int16_t WebRtcOpus_SetPacketLossRate(OpusEncInst* inst, int32_t loss_rate) {
if (inst) {
return opus_encoder_ctl(inst->encoder,
OPUS_SET_PACKET_LOSS_PERC(loss_rate));
} else {
return -1;
}
}
int16_t WebRtcOpus_SetMaxPlaybackRate(OpusEncInst* inst, int32_t frequency_hz) {
opus_int32 set_bandwidth;
if (!inst)
return -1;
if (frequency_hz <= 8000) {
set_bandwidth = OPUS_BANDWIDTH_NARROWBAND;
} else if (frequency_hz <= 12000) {
set_bandwidth = OPUS_BANDWIDTH_MEDIUMBAND;
} else if (frequency_hz <= 16000) {
set_bandwidth = OPUS_BANDWIDTH_WIDEBAND;
} else if (frequency_hz <= 24000) {
set_bandwidth = OPUS_BANDWIDTH_SUPERWIDEBAND;
} else {
set_bandwidth = OPUS_BANDWIDTH_FULLBAND;
}
return opus_encoder_ctl(inst->encoder,
OPUS_SET_MAX_BANDWIDTH(set_bandwidth));
}
int16_t WebRtcOpus_EnableFec(OpusEncInst* inst) {
if (inst) {
return opus_encoder_ctl(inst->encoder, OPUS_SET_INBAND_FEC(1));
} else {
return -1;
}
}
int16_t WebRtcOpus_DisableFec(OpusEncInst* inst) {
if (inst) {
return opus_encoder_ctl(inst->encoder, OPUS_SET_INBAND_FEC(0));
} else {
return -1;
}
}
int16_t WebRtcOpus_EnableDtx(OpusEncInst* inst) {
if (!inst) {
return -1;
}
// To prevent Opus from entering CELT-only mode by forcing signal type to
// voice to make sure that DTX behaves correctly. Currently, DTX does not
// last long during a pure silence, if the signal type is not forced.
// TODO(minyue): Remove the signal type forcing when Opus DTX works properly
// without it.
int ret = opus_encoder_ctl(inst->encoder,
OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
if (ret != OPUS_OK)
return ret;
return opus_encoder_ctl(inst->encoder, OPUS_SET_DTX(1));
}
int16_t WebRtcOpus_DisableDtx(OpusEncInst* inst) {
if (inst) {
int ret = opus_encoder_ctl(inst->encoder,
OPUS_SET_SIGNAL(OPUS_AUTO));
if (ret != OPUS_OK)
return ret;
return opus_encoder_ctl(inst->encoder, OPUS_SET_DTX(0));
} else {
return -1;
}
}
int16_t WebRtcOpus_EnableCbr(OpusEncInst* inst) {
if (inst) {
return opus_encoder_ctl(inst->encoder, OPUS_SET_VBR(0));
} else {
return -1;
}
}
int16_t WebRtcOpus_DisableCbr(OpusEncInst* inst) {
if (inst) {
return opus_encoder_ctl(inst->encoder, OPUS_SET_VBR(1));
} else {
return -1;
}
}
int16_t WebRtcOpus_SetComplexity(OpusEncInst* inst, int32_t complexity) {
if (inst) {
return opus_encoder_ctl(inst->encoder, OPUS_SET_COMPLEXITY(complexity));
} else {
return -1;
}
}
int16_t WebRtcOpus_SetForceChannels(OpusEncInst* inst, size_t num_channels) {
if (!inst)
return -1;
if (num_channels == 0) {
return opus_encoder_ctl(inst->encoder,
OPUS_SET_FORCE_CHANNELS(OPUS_AUTO));
} else if (num_channels == 1 || num_channels == 2) {
return opus_encoder_ctl(inst->encoder,
OPUS_SET_FORCE_CHANNELS(num_channels));
} else {
return -1;
}
}
int16_t WebRtcOpus_DecoderCreate(OpusDecInst** inst, size_t channels) {
int error;
OpusDecInst* state;
if (inst != NULL) {
/* Create Opus decoder state. */
state = (OpusDecInst*) calloc(1, sizeof(OpusDecInst));
if (state == NULL) {
return -1;
}
/* Create new memory, always at 48000 Hz. */
state->decoder = opus_decoder_create(48000, (int)channels, &error);
if (error == OPUS_OK && state->decoder != NULL) {
/* Creation of memory all ok. */
state->channels = channels;
state->prev_decoded_samples = kWebRtcOpusDefaultFrameSize;
state->in_dtx_mode = 0;
*inst = state;
return 0;
}
/* If memory allocation was unsuccessful, free the entire state. */
if (state->decoder) {
opus_decoder_destroy(state->decoder);
}
free(state);
}
return -1;
}
int16_t WebRtcOpus_DecoderFree(OpusDecInst* inst) {
if (inst) {
opus_decoder_destroy(inst->decoder);
free(inst);
return 0;
} else {
return -1;
}
}
size_t WebRtcOpus_DecoderChannels(OpusDecInst* inst) {
return inst->channels;
}
void WebRtcOpus_DecoderInit(OpusDecInst* inst) {
opus_decoder_ctl(inst->decoder, OPUS_RESET_STATE);
inst->in_dtx_mode = 0;
}
/* For decoder to determine if it is to output speech or comfort noise. */
static int16_t DetermineAudioType(OpusDecInst* inst, size_t encoded_bytes) {
// Audio type becomes comfort noise if |encoded_byte| is 1 and keeps
// to be so if the following |encoded_byte| are 0 or 1.
if (encoded_bytes == 0 && inst->in_dtx_mode) {
return 2; // Comfort noise.
} else if (encoded_bytes == 1 || encoded_bytes == 2) {
// TODO(henrik.lundin): There is a slight risk that a 2-byte payload is in
// fact a 1-byte TOC with a 1-byte payload. That will be erroneously
// interpreted as comfort noise output, but such a payload is probably
// faulty anyway.
inst->in_dtx_mode = 1;
return 2; // Comfort noise.
} else {
inst->in_dtx_mode = 0;
return 0; // Speech.
}
}
/* |frame_size| is set to maximum Opus frame size in the normal case, and
* is set to the number of samples needed for PLC in case of losses.
* It is up to the caller to make sure the value is correct. */
static int DecodeNative(OpusDecInst* inst, const uint8_t* encoded,
size_t encoded_bytes, int frame_size,
int16_t* decoded, int16_t* audio_type, int decode_fec) {
int res = opus_decode(inst->decoder, encoded, (opus_int32)encoded_bytes,
(opus_int16*)decoded, frame_size, decode_fec);
if (res <= 0)
return -1;
*audio_type = DetermineAudioType(inst, encoded_bytes);
return res;
}
int WebRtcOpus_Decode(OpusDecInst* inst, const uint8_t* encoded,
size_t encoded_bytes, int16_t* decoded,
int16_t* audio_type) {
int decoded_samples;
if (encoded_bytes == 0) {
*audio_type = DetermineAudioType(inst, encoded_bytes);
decoded_samples = WebRtcOpus_DecodePlc(inst, decoded, 1);
} else {
decoded_samples = DecodeNative(inst,
encoded,
encoded_bytes,
kWebRtcOpusMaxFrameSizePerChannel,
decoded,
audio_type,
0);
}
if (decoded_samples < 0) {
return -1;
}
/* Update decoded sample memory, to be used by the PLC in case of losses. */
inst->prev_decoded_samples = decoded_samples;
return decoded_samples;
}
int WebRtcOpus_DecodePlc(OpusDecInst* inst, int16_t* decoded,
int number_of_lost_frames) {
int16_t audio_type = 0;
int decoded_samples;
int plc_samples;
/* The number of samples we ask for is |number_of_lost_frames| times
* |prev_decoded_samples_|. Limit the number of samples to maximum
* |kWebRtcOpusMaxFrameSizePerChannel|. */
plc_samples = number_of_lost_frames * inst->prev_decoded_samples;
plc_samples = (plc_samples <= kWebRtcOpusMaxFrameSizePerChannel) ?
plc_samples : kWebRtcOpusMaxFrameSizePerChannel;
decoded_samples = DecodeNative(inst, NULL, 0, plc_samples,
decoded, &audio_type, 0);
if (decoded_samples < 0) {
return -1;
}
return decoded_samples;
}
int WebRtcOpus_DecodeFec(OpusDecInst* inst, const uint8_t* encoded,
size_t encoded_bytes, int16_t* decoded,
int16_t* audio_type) {
int decoded_samples;
int fec_samples;
if (WebRtcOpus_PacketHasFec(encoded, encoded_bytes) != 1) {
return 0;
}
fec_samples = opus_packet_get_samples_per_frame(encoded, 48000);
decoded_samples = DecodeNative(inst, encoded, encoded_bytes,
fec_samples, decoded, audio_type, 1);
if (decoded_samples < 0) {
return -1;
}
return decoded_samples;
}
int WebRtcOpus_DurationEst(OpusDecInst* inst,
const uint8_t* payload,
size_t payload_length_bytes) {
if (payload_length_bytes == 0) {
// WebRtcOpus_Decode calls PLC when payload length is zero. So we return
// PLC duration correspondingly.
return WebRtcOpus_PlcDuration(inst);
}
int frames, samples;
frames = opus_packet_get_nb_frames(payload, (opus_int32)payload_length_bytes);
if (frames < 0) {
/* Invalid payload data. */
return 0;
}
samples = frames * opus_packet_get_samples_per_frame(payload, 48000);
if (samples < 120 || samples > 5760) {
/* Invalid payload duration. */
return 0;
}
return samples;
}
int WebRtcOpus_PlcDuration(OpusDecInst* inst) {
/* The number of samples we ask for is |number_of_lost_frames| times
* |prev_decoded_samples_|. Limit the number of samples to maximum
* |kWebRtcOpusMaxFrameSizePerChannel|. */
const int plc_samples = inst->prev_decoded_samples;
return (plc_samples <= kWebRtcOpusMaxFrameSizePerChannel) ?
plc_samples : kWebRtcOpusMaxFrameSizePerChannel;
}
int WebRtcOpus_FecDurationEst(const uint8_t* payload,
size_t payload_length_bytes) {
int samples;
if (WebRtcOpus_PacketHasFec(payload, payload_length_bytes) != 1) {
return 0;
}
samples = opus_packet_get_samples_per_frame(payload, 48000);
if (samples < 480 || samples > 5760) {
/* Invalid payload duration. */
return 0;
}
return samples;
}
int WebRtcOpus_PacketHasFec(const uint8_t* payload,
size_t payload_length_bytes) {
int frames, channels, payload_length_ms;
int n;
opus_int16 frame_sizes[48];
const unsigned char *frame_data[48];
if (payload == NULL || payload_length_bytes == 0)
return 0;
/* In CELT_ONLY mode, packets should not have FEC. */
if (payload[0] & 0x80)
return 0;
payload_length_ms = opus_packet_get_samples_per_frame(payload, 48000) / 48;
if (10 > payload_length_ms)
payload_length_ms = 10;
channels = opus_packet_get_nb_channels(payload);
switch (payload_length_ms) {
case 10:
case 20: {
frames = 1;
break;
}
case 40: {
frames = 2;
break;
}
case 60: {
frames = 3;
break;
}
default: {
return 0; // It is actually even an invalid packet.
}
}
/* The following is to parse the LBRR flags. */
if (opus_packet_parse(payload, (opus_int32)payload_length_bytes, NULL,
frame_data, frame_sizes, NULL) < 0) {
return 0;
}
if (frame_sizes[0] <= 1) {
return 0;
}
for (n = 0; n < channels; n++) {
if (frame_data[0][0] & (0x80 >> ((n + 1) * (frames + 1) - 1)))
return 1;
}
return 0;
}

View File

@ -0,0 +1,397 @@
/*
* 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 WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_OPUS_INTERFACE_H_
#define WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_OPUS_INTERFACE_H_
#include <stddef.h>
#include "webrtc/typedefs.h"
#ifdef __cplusplus
extern "C" {
#endif
// Opaque wrapper types for the codec state.
typedef struct WebRtcOpusEncInst OpusEncInst;
typedef struct WebRtcOpusDecInst OpusDecInst;
/****************************************************************************
* WebRtcOpus_EncoderCreate(...)
*
* This function create an Opus encoder.
*
* Input:
* - channels : number of channels.
* - application : 0 - VOIP applications.
* Favor speech intelligibility.
* 1 - Audio applications.
* Favor faithfulness to the original input.
*
* Output:
* - inst : a pointer to Encoder context that is created
* if success.
*
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_EncoderCreate(OpusEncInst** inst,
size_t channels,
int32_t application);
int16_t WebRtcOpus_EncoderFree(OpusEncInst* inst);
/****************************************************************************
* WebRtcOpus_Encode(...)
*
* This function encodes audio as a series of Opus frames and inserts
* it into a packet. Input buffer can be any length.
*
* Input:
* - inst : Encoder context
* - audio_in : Input speech data buffer
* - samples : Samples per channel in audio_in
* - length_encoded_buffer : Output buffer size
*
* Output:
* - encoded : Output compressed data buffer
*
* Return value : >=0 - Length (in bytes) of coded data
* -1 - Error
*/
int WebRtcOpus_Encode(OpusEncInst* inst,
const int16_t* audio_in,
size_t samples,
size_t length_encoded_buffer,
uint8_t* encoded);
/****************************************************************************
* WebRtcOpus_SetBitRate(...)
*
* This function adjusts the target bitrate of the encoder.
*
* Input:
* - inst : Encoder context
* - rate : New target bitrate
*
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_SetBitRate(OpusEncInst* inst, int32_t rate);
/****************************************************************************
* WebRtcOpus_SetPacketLossRate(...)
*
* This function configures the encoder's expected packet loss percentage.
*
* Input:
* - inst : Encoder context
* - loss_rate : loss percentage in the range 0-100, inclusive.
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_SetPacketLossRate(OpusEncInst* inst, int32_t loss_rate);
/****************************************************************************
* WebRtcOpus_SetMaxPlaybackRate(...)
*
* Configures the maximum playback rate for encoding. Due to hardware
* limitations, the receiver may render audio up to a playback rate. Opus
* encoder can use this information to optimize for network usage and encoding
* complexity. This will affect the audio bandwidth in the coded audio. However,
* the input/output sample rate is not affected.
*
* Input:
* - inst : Encoder context
* - frequency_hz : Maximum playback rate in Hz.
* This parameter can take any value. The relation
* between the value and the Opus internal mode is
* as following:
* frequency_hz <= 8000 narrow band
* 8000 < frequency_hz <= 12000 medium band
* 12000 < frequency_hz <= 16000 wide band
* 16000 < frequency_hz <= 24000 super wide band
* frequency_hz > 24000 full band
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_SetMaxPlaybackRate(OpusEncInst* inst, int32_t frequency_hz);
/* TODO(minyue): Check whether an API to check the FEC and the packet loss rate
* is needed. It might not be very useful since there are not many use cases and
* the caller can always maintain the states. */
/****************************************************************************
* WebRtcOpus_EnableFec()
*
* This function enables FEC for encoding.
*
* Input:
* - inst : Encoder context
*
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_EnableFec(OpusEncInst* inst);
/****************************************************************************
* WebRtcOpus_DisableFec()
*
* This function disables FEC for encoding.
*
* Input:
* - inst : Encoder context
*
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_DisableFec(OpusEncInst* inst);
/****************************************************************************
* WebRtcOpus_EnableDtx()
*
* This function enables Opus internal DTX for encoding.
*
* Input:
* - inst : Encoder context
*
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_EnableDtx(OpusEncInst* inst);
/****************************************************************************
* WebRtcOpus_DisableDtx()
*
* This function disables Opus internal DTX for encoding.
*
* Input:
* - inst : Encoder context
*
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_DisableDtx(OpusEncInst* inst);
/****************************************************************************
* WebRtcOpus_EnableCbr()
*
* This function enables CBR for encoding.
*
* Input:
* - inst : Encoder context
*
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_EnableCbr(OpusEncInst* inst);
/****************************************************************************
* WebRtcOpus_DisableCbr()
*
* This function disables CBR for encoding.
*
* Input:
* - inst : Encoder context
*
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_DisableCbr(OpusEncInst* inst);
/*
* WebRtcOpus_SetComplexity(...)
*
* This function adjusts the computational complexity. The effect is the same as
* calling the complexity setting of Opus as an Opus encoder related CTL.
*
* Input:
* - inst : Encoder context
* - complexity : New target complexity (0-10, inclusive)
*
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_SetComplexity(OpusEncInst* inst, int32_t complexity);
/*
* WebRtcOpus_SetForceChannels(...)
*
* If the encoder is initialized as a stereo encoder, Opus will by default
* decide whether to encode in mono or stereo based on the bitrate. This
* function overrules the previous setting, and forces the encoder to encode
* in auto/mono/stereo.
*
* If the Encoder is initialized as a mono encoder, and one tries to force
* stereo, the function will return an error.
*
* Input:
* - inst : Encoder context
* - num_channels : 0 - Not forced
* 1 - Mono
* 2 - Stereo
*
* Return value : 0 - Success
* -1 - Error
*/
int16_t WebRtcOpus_SetForceChannels(OpusEncInst* inst, size_t num_channels);
int16_t WebRtcOpus_DecoderCreate(OpusDecInst** inst, size_t channels);
int16_t WebRtcOpus_DecoderFree(OpusDecInst* inst);
/****************************************************************************
* WebRtcOpus_DecoderChannels(...)
*
* This function returns the number of channels created for Opus decoder.
*/
size_t WebRtcOpus_DecoderChannels(OpusDecInst* inst);
/****************************************************************************
* WebRtcOpus_DecoderInit(...)
*
* This function resets state of the decoder.
*
* Input:
* - inst : Decoder context
*/
void WebRtcOpus_DecoderInit(OpusDecInst* inst);
/****************************************************************************
* WebRtcOpus_Decode(...)
*
* This function decodes an Opus packet into one or more audio frames at the
* ACM interface's sampling rate (32 kHz).
*
* Input:
* - inst : Decoder context
* - encoded : Encoded data
* - encoded_bytes : Bytes in encoded vector
*
* Output:
* - decoded : The decoded vector
* - audio_type : 1 normal, 2 CNG (for Opus it should
* always return 1 since we're not using Opus's
* built-in DTX/CNG scheme)
*
* Return value : >0 - Samples per channel in decoded vector
* -1 - Error
*/
int WebRtcOpus_Decode(OpusDecInst* inst, const uint8_t* encoded,
size_t encoded_bytes, int16_t* decoded,
int16_t* audio_type);
/****************************************************************************
* WebRtcOpus_DecodePlc(...)
*
* This function processes PLC for opus frame(s).
* Input:
* - inst : Decoder context
* - number_of_lost_frames : Number of PLC frames to produce
*
* Output:
* - decoded : The decoded vector
*
* Return value : >0 - number of samples in decoded PLC vector
* -1 - Error
*/
int WebRtcOpus_DecodePlc(OpusDecInst* inst, int16_t* decoded,
int number_of_lost_frames);
/****************************************************************************
* WebRtcOpus_DecodeFec(...)
*
* This function decodes the FEC data from an Opus packet into one or more audio
* frames at the ACM interface's sampling rate (32 kHz).
*
* Input:
* - inst : Decoder context
* - encoded : Encoded data
* - encoded_bytes : Bytes in encoded vector
*
* Output:
* - decoded : The decoded vector (previous frame)
*
* Return value : >0 - Samples per channel in decoded vector
* 0 - No FEC data in the packet
* -1 - Error
*/
int WebRtcOpus_DecodeFec(OpusDecInst* inst, const uint8_t* encoded,
size_t encoded_bytes, int16_t* decoded,
int16_t* audio_type);
/****************************************************************************
* WebRtcOpus_DurationEst(...)
*
* This function calculates the duration of an opus packet.
* Input:
* - inst : Decoder context
* - payload : Encoded data pointer
* - payload_length_bytes : Bytes of encoded data
*
* Return value : The duration of the packet, in samples per
* channel.
*/
int WebRtcOpus_DurationEst(OpusDecInst* inst,
const uint8_t* payload,
size_t payload_length_bytes);
/****************************************************************************
* WebRtcOpus_PlcDuration(...)
*
* This function calculates the duration of a frame returned by packet loss
* concealment (PLC).
*
* Input:
* - inst : Decoder context
*
* Return value : The duration of a frame returned by PLC, in
* samples per channel.
*/
int WebRtcOpus_PlcDuration(OpusDecInst* inst);
/* TODO(minyue): Check whether it is needed to add a decoder context to the
* arguments, like WebRtcOpus_DurationEst(...). In fact, the packet itself tells
* the duration. The decoder context in WebRtcOpus_DurationEst(...) is not used.
* So it may be advisable to remove it from WebRtcOpus_DurationEst(...). */
/****************************************************************************
* WebRtcOpus_FecDurationEst(...)
*
* This function calculates the duration of the FEC data within an opus packet.
* Input:
* - payload : Encoded data pointer
* - payload_length_bytes : Bytes of encoded data
*
* Return value : >0 - The duration of the FEC data in the
* packet in samples per channel.
* 0 - No FEC data in the packet.
*/
int WebRtcOpus_FecDurationEst(const uint8_t* payload,
size_t payload_length_bytes);
/****************************************************************************
* WebRtcOpus_PacketHasFec(...)
*
* This function detects if an opus packet has FEC.
* Input:
* - payload : Encoded data pointer
* - payload_length_bytes : Bytes of encoded data
*
* Return value : 0 - the packet does NOT contain FEC.
* 1 - the packet contains FEC.
*/
int WebRtcOpus_PacketHasFec(const uint8_t* payload,
size_t payload_length_bytes);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_OPUS_INTERFACE_H_

View File

@ -0,0 +1,121 @@
/*
* 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 "webrtc/modules/audio_coding/codecs/opus/opus_interface.h"
#include "webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_test.h"
using ::std::string;
namespace webrtc {
static const int kOpusBlockDurationMs = 20;
static const int kOpusSamplingKhz = 48;
class OpusSpeedTest : public AudioCodecSpeedTest {
protected:
OpusSpeedTest();
void SetUp() override;
void TearDown() override;
float EncodeABlock(int16_t* in_data, uint8_t* bit_stream,
size_t max_bytes, size_t* encoded_bytes) override;
float DecodeABlock(const uint8_t* bit_stream, size_t encoded_bytes,
int16_t* out_data) override;
WebRtcOpusEncInst* opus_encoder_;
WebRtcOpusDecInst* opus_decoder_;
};
OpusSpeedTest::OpusSpeedTest()
: AudioCodecSpeedTest(kOpusBlockDurationMs,
kOpusSamplingKhz,
kOpusSamplingKhz),
opus_encoder_(NULL),
opus_decoder_(NULL) {
}
void OpusSpeedTest::SetUp() {
AudioCodecSpeedTest::SetUp();
// If channels_ == 1, use Opus VOIP mode, otherwise, audio mode.
int app = channels_ == 1 ? 0 : 1;
/* Create encoder memory. */
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_, channels_, app));
EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_, channels_));
/* Set bitrate. */
EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_, bit_rate_));
}
void OpusSpeedTest::TearDown() {
AudioCodecSpeedTest::TearDown();
/* Free memory. */
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
}
float OpusSpeedTest::EncodeABlock(int16_t* in_data, uint8_t* bit_stream,
size_t max_bytes, size_t* encoded_bytes) {
clock_t clocks = clock();
int value = WebRtcOpus_Encode(opus_encoder_, in_data,
input_length_sample_, max_bytes,
bit_stream);
clocks = clock() - clocks;
EXPECT_GT(value, 0);
*encoded_bytes = static_cast<size_t>(value);
return 1000.0 * clocks / CLOCKS_PER_SEC;
}
float OpusSpeedTest::DecodeABlock(const uint8_t* bit_stream,
size_t encoded_bytes, int16_t* out_data) {
int value;
int16_t audio_type;
clock_t clocks = clock();
value = WebRtcOpus_Decode(opus_decoder_, bit_stream, encoded_bytes, out_data,
&audio_type);
clocks = clock() - clocks;
EXPECT_EQ(output_length_sample_, static_cast<size_t>(value));
return 1000.0 * clocks / CLOCKS_PER_SEC;
}
#define ADD_TEST(complexity) \
TEST_P(OpusSpeedTest, OpusSetComplexityTest##complexity) { \
/* Test audio length in second. */ \
size_t kDurationSec = 400; \
/* Set complexity. */ \
printf("Setting complexity to %d ...\n", complexity); \
EXPECT_EQ(0, WebRtcOpus_SetComplexity(opus_encoder_, complexity)); \
EncodeDecode(kDurationSec); \
}
ADD_TEST(10);
ADD_TEST(9);
ADD_TEST(8);
ADD_TEST(7);
ADD_TEST(6);
ADD_TEST(5);
ADD_TEST(4);
ADD_TEST(3);
ADD_TEST(2);
ADD_TEST(1);
ADD_TEST(0);
// List all test cases: (channel, bit rat, filename, extension).
const coding_param param_set[] =
{::std::tr1::make_tuple(1, 64000,
string("audio_coding/speech_mono_32_48kHz"),
string("pcm"), true),
::std::tr1::make_tuple(1, 32000,
string("audio_coding/speech_mono_32_48kHz"),
string("pcm"), true),
::std::tr1::make_tuple(2, 64000,
string("audio_coding/music_stereo_48kHz"),
string("pcm"), true)};
INSTANTIATE_TEST_CASE_P(AllTest, OpusSpeedTest,
::testing::ValuesIn(param_set));
} // namespace webrtc

View File

@ -0,0 +1,774 @@
/*
* 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 <memory>
#include <string>
#include "webrtc/modules/audio_coding/codecs/opus/opus_inst.h"
#include "webrtc/modules/audio_coding/codecs/opus/opus_interface.h"
#include "webrtc/modules/audio_coding/neteq/tools/audio_loop.h"
#include "webrtc/rtc_base/checks.h"
#include "webrtc/test/gtest.h"
#include "webrtc/test/testsupport/fileutils.h"
namespace webrtc {
using test::AudioLoop;
using ::testing::TestWithParam;
using ::testing::Values;
using ::testing::Combine;
// Maximum number of bytes in output bitstream.
const size_t kMaxBytes = 1000;
// Sample rate of Opus.
const size_t kOpusRateKhz = 48;
// Number of samples-per-channel in a 20 ms frame, sampled at 48 kHz.
const size_t kOpus20msFrameSamples = kOpusRateKhz * 20;
// Number of samples-per-channel in a 10 ms frame, sampled at 48 kHz.
const size_t kOpus10msFrameSamples = kOpusRateKhz * 10;
class OpusTest : public TestWithParam<::testing::tuple<int, int>> {
protected:
OpusTest();
void TestDtxEffect(bool dtx, int block_length_ms);
void TestCbrEffect(bool dtx, int block_length_ms);
// Prepare |speech_data_| for encoding, read from a hard-coded file.
// After preparation, |speech_data_.GetNextBlock()| returns a pointer to a
// block of |block_length_ms| milliseconds. The data is looped every
// |loop_length_ms| milliseconds.
void PrepareSpeechData(size_t channel,
int block_length_ms,
int loop_length_ms);
int EncodeDecode(WebRtcOpusEncInst* encoder,
rtc::ArrayView<const int16_t> input_audio,
WebRtcOpusDecInst* decoder,
int16_t* output_audio,
int16_t* audio_type);
void SetMaxPlaybackRate(WebRtcOpusEncInst* encoder,
opus_int32 expect, int32_t set);
void CheckAudioBounded(const int16_t* audio, size_t samples, size_t channels,
uint16_t bound) const;
WebRtcOpusEncInst* opus_encoder_;
WebRtcOpusDecInst* opus_decoder_;
AudioLoop speech_data_;
uint8_t bitstream_[kMaxBytes];
size_t encoded_bytes_;
size_t channels_;
int application_;
};
OpusTest::OpusTest()
: opus_encoder_(NULL),
opus_decoder_(NULL),
encoded_bytes_(0),
channels_(static_cast<size_t>(::testing::get<0>(GetParam()))),
application_(::testing::get<1>(GetParam())) {
}
void OpusTest::PrepareSpeechData(size_t channel, int block_length_ms,
int loop_length_ms) {
const std::string file_name =
webrtc::test::ResourcePath((channel == 1) ?
"audio_coding/testfile32kHz" :
"audio_coding/teststereo32kHz", "pcm");
if (loop_length_ms < block_length_ms) {
loop_length_ms = block_length_ms;
}
EXPECT_TRUE(speech_data_.Init(file_name,
loop_length_ms * kOpusRateKhz * channel,
block_length_ms * kOpusRateKhz * channel));
}
void OpusTest::SetMaxPlaybackRate(WebRtcOpusEncInst* encoder,
opus_int32 expect,
int32_t set) {
opus_int32 bandwidth;
EXPECT_EQ(0, WebRtcOpus_SetMaxPlaybackRate(opus_encoder_, set));
opus_encoder_ctl(opus_encoder_->encoder,
OPUS_GET_MAX_BANDWIDTH(&bandwidth));
EXPECT_EQ(expect, bandwidth);
}
void OpusTest::CheckAudioBounded(const int16_t* audio, size_t samples,
size_t channels, uint16_t bound) const {
for (size_t i = 0; i < samples; ++i) {
for (size_t c = 0; c < channels; ++c) {
ASSERT_GE(audio[i * channels + c], -bound);
ASSERT_LE(audio[i * channels + c], bound);
}
}
}
int OpusTest::EncodeDecode(WebRtcOpusEncInst* encoder,
rtc::ArrayView<const int16_t> input_audio,
WebRtcOpusDecInst* decoder,
int16_t* output_audio,
int16_t* audio_type) {
int encoded_bytes_int = WebRtcOpus_Encode(
encoder, input_audio.data(),
rtc::CheckedDivExact(input_audio.size(), channels_),
kMaxBytes, bitstream_);
EXPECT_GE(encoded_bytes_int, 0);
encoded_bytes_ = static_cast<size_t>(encoded_bytes_int);
int est_len = WebRtcOpus_DurationEst(decoder, bitstream_, encoded_bytes_);
int act_len = WebRtcOpus_Decode(decoder, bitstream_,
encoded_bytes_, output_audio,
audio_type);
EXPECT_EQ(est_len, act_len);
return act_len;
}
// Test if encoder/decoder can enter DTX mode properly and do not enter DTX when
// they should not. This test is signal dependent.
void OpusTest::TestDtxEffect(bool dtx, int block_length_ms) {
PrepareSpeechData(channels_, block_length_ms, 2000);
const size_t samples = kOpusRateKhz * block_length_ms;
// Create encoder memory.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_, channels_));
// Set bitrate.
EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_,
channels_ == 1 ? 32000 : 64000));
// Set input audio as silence.
std::vector<int16_t> silence(samples * channels_, 0);
// Setting DTX.
EXPECT_EQ(0, dtx ? WebRtcOpus_EnableDtx(opus_encoder_) :
WebRtcOpus_DisableDtx(opus_encoder_));
int16_t audio_type;
int16_t* output_data_decode = new int16_t[samples * channels_];
for (int i = 0; i < 100; ++i) {
EXPECT_EQ(samples,
static_cast<size_t>(EncodeDecode(
opus_encoder_, speech_data_.GetNextBlock(), opus_decoder_,
output_data_decode, &audio_type)));
// If not DTX, it should never enter DTX mode. If DTX, we do not care since
// whether it enters DTX depends on the signal type.
if (!dtx) {
EXPECT_GT(encoded_bytes_, 1U);
EXPECT_EQ(0, opus_encoder_->in_dtx_mode);
EXPECT_EQ(0, opus_decoder_->in_dtx_mode);
EXPECT_EQ(0, audio_type); // Speech.
}
}
// We input some silent segments. In DTX mode, the encoder will stop sending.
// However, DTX may happen after a while.
for (int i = 0; i < 30; ++i) {
EXPECT_EQ(samples,
static_cast<size_t>(EncodeDecode(
opus_encoder_, silence, opus_decoder_, output_data_decode,
&audio_type)));
if (!dtx) {
EXPECT_GT(encoded_bytes_, 1U);
EXPECT_EQ(0, opus_encoder_->in_dtx_mode);
EXPECT_EQ(0, opus_decoder_->in_dtx_mode);
EXPECT_EQ(0, audio_type); // Speech.
} else if (encoded_bytes_ == 1) {
EXPECT_EQ(1, opus_encoder_->in_dtx_mode);
EXPECT_EQ(1, opus_decoder_->in_dtx_mode);
EXPECT_EQ(2, audio_type); // Comfort noise.
break;
}
}
// When Opus is in DTX, it wakes up in a regular basis. It sends two packets,
// one with an arbitrary size and the other of 1-byte, then stops sending for
// a certain number of frames.
// |max_dtx_frames| is the maximum number of frames Opus can stay in DTX.
const int max_dtx_frames = 400 / block_length_ms + 1;
// We run |kRunTimeMs| milliseconds of pure silence.
const int kRunTimeMs = 4500;
// We check that, after a |kCheckTimeMs| milliseconds (given that the CNG in
// Opus needs time to adapt), the absolute values of DTX decoded signal are
// bounded by |kOutputValueBound|.
const int kCheckTimeMs = 4000;
#if defined(OPUS_FIXED_POINT)
// Fixed-point Opus generates a random (comfort) noise, which has a less
// predictable value bound than its floating-point Opus. This value depends on
// input signal, and the time window for checking the output values (between
// |kCheckTimeMs| and |kRunTimeMs|).
const uint16_t kOutputValueBound = 30;
#else
const uint16_t kOutputValueBound = 2;
#endif
int time = 0;
while (time < kRunTimeMs) {
// DTX mode is maintained for maximum |max_dtx_frames| frames.
int i = 0;
for (; i < max_dtx_frames; ++i) {
time += block_length_ms;
EXPECT_EQ(samples,
static_cast<size_t>(EncodeDecode(
opus_encoder_, silence, opus_decoder_, output_data_decode,
&audio_type)));
if (dtx) {
if (encoded_bytes_ > 1)
break;
EXPECT_EQ(0U, encoded_bytes_) // Send 0 byte.
<< "Opus should have entered DTX mode.";
EXPECT_EQ(1, opus_encoder_->in_dtx_mode);
EXPECT_EQ(1, opus_decoder_->in_dtx_mode);
EXPECT_EQ(2, audio_type); // Comfort noise.
if (time >= kCheckTimeMs) {
CheckAudioBounded(output_data_decode, samples, channels_,
kOutputValueBound);
}
} else {
EXPECT_GT(encoded_bytes_, 1U);
EXPECT_EQ(0, opus_encoder_->in_dtx_mode);
EXPECT_EQ(0, opus_decoder_->in_dtx_mode);
EXPECT_EQ(0, audio_type); // Speech.
}
}
if (dtx) {
// With DTX, Opus must stop transmission for some time.
EXPECT_GT(i, 1);
}
// We expect a normal payload.
EXPECT_EQ(0, opus_encoder_->in_dtx_mode);
EXPECT_EQ(0, opus_decoder_->in_dtx_mode);
EXPECT_EQ(0, audio_type); // Speech.
// Enters DTX again immediately.
time += block_length_ms;
EXPECT_EQ(samples,
static_cast<size_t>(EncodeDecode(
opus_encoder_, silence, opus_decoder_, output_data_decode,
&audio_type)));
if (dtx) {
EXPECT_EQ(1U, encoded_bytes_); // Send 1 byte.
EXPECT_EQ(1, opus_encoder_->in_dtx_mode);
EXPECT_EQ(1, opus_decoder_->in_dtx_mode);
EXPECT_EQ(2, audio_type); // Comfort noise.
if (time >= kCheckTimeMs) {
CheckAudioBounded(output_data_decode, samples, channels_,
kOutputValueBound);
}
} else {
EXPECT_GT(encoded_bytes_, 1U);
EXPECT_EQ(0, opus_encoder_->in_dtx_mode);
EXPECT_EQ(0, opus_decoder_->in_dtx_mode);
EXPECT_EQ(0, audio_type); // Speech.
}
}
silence[0] = 10000;
if (dtx) {
// Verify that encoder/decoder can jump out from DTX mode.
EXPECT_EQ(samples,
static_cast<size_t>(EncodeDecode(
opus_encoder_, silence, opus_decoder_, output_data_decode,
&audio_type)));
EXPECT_GT(encoded_bytes_, 1U);
EXPECT_EQ(0, opus_encoder_->in_dtx_mode);
EXPECT_EQ(0, opus_decoder_->in_dtx_mode);
EXPECT_EQ(0, audio_type); // Speech.
}
// Free memory.
delete[] output_data_decode;
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
}
// Test if CBR does what we expect.
void OpusTest::TestCbrEffect(bool cbr, int block_length_ms) {
PrepareSpeechData(channels_, block_length_ms, 2000);
const size_t samples = kOpusRateKhz * block_length_ms;
int32_t max_pkt_size_diff = 0;
int32_t prev_pkt_size = 0;
// Create encoder memory.
EXPECT_EQ(0,
WebRtcOpus_EncoderCreate(&opus_encoder_, channels_, application_));
EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_, channels_));
// Set bitrate.
EXPECT_EQ(
0, WebRtcOpus_SetBitRate(opus_encoder_, channels_ == 1 ? 32000 : 64000));
// Setting CBR.
EXPECT_EQ(0, cbr ? WebRtcOpus_EnableCbr(opus_encoder_)
: WebRtcOpus_DisableCbr(opus_encoder_));
int16_t audio_type;
std::vector<int16_t> audio_out(samples * channels_);
for (int i = 0; i < 100; ++i) {
EXPECT_EQ(samples, static_cast<size_t>(EncodeDecode(
opus_encoder_, speech_data_.GetNextBlock(),
opus_decoder_, audio_out.data(), &audio_type)));
if (prev_pkt_size > 0) {
int32_t diff = std::abs((int32_t)encoded_bytes_ - prev_pkt_size);
max_pkt_size_diff = std::max(max_pkt_size_diff, diff);
}
prev_pkt_size = encoded_bytes_;
}
if (cbr) {
EXPECT_EQ(max_pkt_size_diff, 0);
} else {
EXPECT_GT(max_pkt_size_diff, 0);
}
// Free memory.
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
}
// Test failing Create.
TEST(OpusTest, OpusCreateFail) {
WebRtcOpusEncInst* opus_encoder;
WebRtcOpusDecInst* opus_decoder;
// Test to see that an invalid pointer is caught.
EXPECT_EQ(-1, WebRtcOpus_EncoderCreate(NULL, 1, 0));
// Invalid channel number.
EXPECT_EQ(-1, WebRtcOpus_EncoderCreate(&opus_encoder, 3, 0));
// Invalid applciation mode.
EXPECT_EQ(-1, WebRtcOpus_EncoderCreate(&opus_encoder, 1, 2));
EXPECT_EQ(-1, WebRtcOpus_DecoderCreate(NULL, 1));
// Invalid channel number.
EXPECT_EQ(-1, WebRtcOpus_DecoderCreate(&opus_decoder, 3));
}
// Test failing Free.
TEST(OpusTest, OpusFreeFail) {
// Test to see that an invalid pointer is caught.
EXPECT_EQ(-1, WebRtcOpus_EncoderFree(NULL));
EXPECT_EQ(-1, WebRtcOpus_DecoderFree(NULL));
}
// Test normal Create and Free.
TEST_P(OpusTest, OpusCreateFree) {
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_, channels_));
EXPECT_TRUE(opus_encoder_ != NULL);
EXPECT_TRUE(opus_decoder_ != NULL);
// Free encoder and decoder memory.
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
}
TEST_P(OpusTest, OpusEncodeDecode) {
PrepareSpeechData(channels_, 20, 20);
// Create encoder memory.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_,
channels_));
// Set bitrate.
EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_,
channels_ == 1 ? 32000 : 64000));
// Check number of channels for decoder.
EXPECT_EQ(channels_, WebRtcOpus_DecoderChannels(opus_decoder_));
// Check application mode.
opus_int32 app;
opus_encoder_ctl(opus_encoder_->encoder,
OPUS_GET_APPLICATION(&app));
EXPECT_EQ(application_ == 0 ? OPUS_APPLICATION_VOIP : OPUS_APPLICATION_AUDIO,
app);
// Encode & decode.
int16_t audio_type;
int16_t* output_data_decode = new int16_t[kOpus20msFrameSamples * channels_];
EXPECT_EQ(kOpus20msFrameSamples,
static_cast<size_t>(
EncodeDecode(opus_encoder_, speech_data_.GetNextBlock(),
opus_decoder_, output_data_decode, &audio_type)));
// Free memory.
delete[] output_data_decode;
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
}
TEST_P(OpusTest, OpusSetBitRate) {
// Test without creating encoder memory.
EXPECT_EQ(-1, WebRtcOpus_SetBitRate(opus_encoder_, 60000));
// Create encoder memory, try with different bitrates.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_, 30000));
EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_, 60000));
EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_, 300000));
EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_, 600000));
// Free memory.
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
}
TEST_P(OpusTest, OpusSetComplexity) {
// Test without creating encoder memory.
EXPECT_EQ(-1, WebRtcOpus_SetComplexity(opus_encoder_, 9));
// Create encoder memory, try with different complexities.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
EXPECT_EQ(0, WebRtcOpus_SetComplexity(opus_encoder_, 0));
EXPECT_EQ(0, WebRtcOpus_SetComplexity(opus_encoder_, 10));
EXPECT_EQ(-1, WebRtcOpus_SetComplexity(opus_encoder_, 11));
// Free memory.
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
}
TEST_P(OpusTest, OpusForceChannels) {
// Test without creating encoder memory.
EXPECT_EQ(-1, WebRtcOpus_SetForceChannels(opus_encoder_, 1));
ASSERT_EQ(0,
WebRtcOpus_EncoderCreate(&opus_encoder_, channels_, application_));
if (channels_ == 2) {
EXPECT_EQ(-1, WebRtcOpus_SetForceChannels(opus_encoder_, 3));
EXPECT_EQ(0, WebRtcOpus_SetForceChannels(opus_encoder_, 2));
EXPECT_EQ(0, WebRtcOpus_SetForceChannels(opus_encoder_, 1));
EXPECT_EQ(0, WebRtcOpus_SetForceChannels(opus_encoder_, 0));
} else {
EXPECT_EQ(-1, WebRtcOpus_SetForceChannels(opus_encoder_, 2));
EXPECT_EQ(0, WebRtcOpus_SetForceChannels(opus_encoder_, 1));
EXPECT_EQ(0, WebRtcOpus_SetForceChannels(opus_encoder_, 0));
}
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
}
// Encode and decode one frame, initialize the decoder and
// decode once more.
TEST_P(OpusTest, OpusDecodeInit) {
PrepareSpeechData(channels_, 20, 20);
// Create encoder memory.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_, channels_));
// Encode & decode.
int16_t audio_type;
int16_t* output_data_decode = new int16_t[kOpus20msFrameSamples * channels_];
EXPECT_EQ(kOpus20msFrameSamples,
static_cast<size_t>(
EncodeDecode(opus_encoder_, speech_data_.GetNextBlock(),
opus_decoder_, output_data_decode, &audio_type)));
WebRtcOpus_DecoderInit(opus_decoder_);
EXPECT_EQ(kOpus20msFrameSamples,
static_cast<size_t>(WebRtcOpus_Decode(
opus_decoder_, bitstream_, encoded_bytes_, output_data_decode,
&audio_type)));
// Free memory.
delete[] output_data_decode;
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
}
TEST_P(OpusTest, OpusEnableDisableFec) {
// Test without creating encoder memory.
EXPECT_EQ(-1, WebRtcOpus_EnableFec(opus_encoder_));
EXPECT_EQ(-1, WebRtcOpus_DisableFec(opus_encoder_));
// Create encoder memory.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
EXPECT_EQ(0, WebRtcOpus_EnableFec(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_DisableFec(opus_encoder_));
// Free memory.
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
}
TEST_P(OpusTest, OpusEnableDisableDtx) {
// Test without creating encoder memory.
EXPECT_EQ(-1, WebRtcOpus_EnableDtx(opus_encoder_));
EXPECT_EQ(-1, WebRtcOpus_DisableDtx(opus_encoder_));
// Create encoder memory.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
opus_int32 dtx;
// DTX is off by default.
opus_encoder_ctl(opus_encoder_->encoder,
OPUS_GET_DTX(&dtx));
EXPECT_EQ(0, dtx);
// Test to enable DTX.
EXPECT_EQ(0, WebRtcOpus_EnableDtx(opus_encoder_));
opus_encoder_ctl(opus_encoder_->encoder,
OPUS_GET_DTX(&dtx));
EXPECT_EQ(1, dtx);
// Test to disable DTX.
EXPECT_EQ(0, WebRtcOpus_DisableDtx(opus_encoder_));
opus_encoder_ctl(opus_encoder_->encoder,
OPUS_GET_DTX(&dtx));
EXPECT_EQ(0, dtx);
// Free memory.
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
}
TEST_P(OpusTest, OpusDtxOff) {
TestDtxEffect(false, 10);
TestDtxEffect(false, 20);
TestDtxEffect(false, 40);
}
TEST_P(OpusTest, OpusDtxOn) {
TestDtxEffect(true, 10);
TestDtxEffect(true, 20);
TestDtxEffect(true, 40);
}
TEST_P(OpusTest, OpusCbrOff) {
TestCbrEffect(false, 10);
TestCbrEffect(false, 20);
TestCbrEffect(false, 40);
}
TEST_P(OpusTest, OpusCbrOn) {
TestCbrEffect(true, 10);
TestCbrEffect(true, 20);
TestCbrEffect(true, 40);
}
TEST_P(OpusTest, OpusSetPacketLossRate) {
// Test without creating encoder memory.
EXPECT_EQ(-1, WebRtcOpus_SetPacketLossRate(opus_encoder_, 50));
// Create encoder memory.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
EXPECT_EQ(0, WebRtcOpus_SetPacketLossRate(opus_encoder_, 50));
EXPECT_EQ(-1, WebRtcOpus_SetPacketLossRate(opus_encoder_, -1));
EXPECT_EQ(-1, WebRtcOpus_SetPacketLossRate(opus_encoder_, 101));
// Free memory.
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
}
TEST_P(OpusTest, OpusSetMaxPlaybackRate) {
// Test without creating encoder memory.
EXPECT_EQ(-1, WebRtcOpus_SetMaxPlaybackRate(opus_encoder_, 20000));
// Create encoder memory.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
SetMaxPlaybackRate(opus_encoder_, OPUS_BANDWIDTH_FULLBAND, 48000);
SetMaxPlaybackRate(opus_encoder_, OPUS_BANDWIDTH_FULLBAND, 24001);
SetMaxPlaybackRate(opus_encoder_, OPUS_BANDWIDTH_SUPERWIDEBAND, 24000);
SetMaxPlaybackRate(opus_encoder_, OPUS_BANDWIDTH_SUPERWIDEBAND, 16001);
SetMaxPlaybackRate(opus_encoder_, OPUS_BANDWIDTH_WIDEBAND, 16000);
SetMaxPlaybackRate(opus_encoder_, OPUS_BANDWIDTH_WIDEBAND, 12001);
SetMaxPlaybackRate(opus_encoder_, OPUS_BANDWIDTH_MEDIUMBAND, 12000);
SetMaxPlaybackRate(opus_encoder_, OPUS_BANDWIDTH_MEDIUMBAND, 8001);
SetMaxPlaybackRate(opus_encoder_, OPUS_BANDWIDTH_NARROWBAND, 8000);
SetMaxPlaybackRate(opus_encoder_, OPUS_BANDWIDTH_NARROWBAND, 4000);
// Free memory.
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
}
// Test PLC.
TEST_P(OpusTest, OpusDecodePlc) {
PrepareSpeechData(channels_, 20, 20);
// Create encoder memory.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_, channels_));
// Set bitrate.
EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_,
channels_== 1 ? 32000 : 64000));
// Check number of channels for decoder.
EXPECT_EQ(channels_, WebRtcOpus_DecoderChannels(opus_decoder_));
// Encode & decode.
int16_t audio_type;
int16_t* output_data_decode = new int16_t[kOpus20msFrameSamples * channels_];
EXPECT_EQ(kOpus20msFrameSamples,
static_cast<size_t>(
EncodeDecode(opus_encoder_, speech_data_.GetNextBlock(),
opus_decoder_, output_data_decode, &audio_type)));
// Call decoder PLC.
int16_t* plc_buffer = new int16_t[kOpus20msFrameSamples * channels_];
EXPECT_EQ(kOpus20msFrameSamples,
static_cast<size_t>(WebRtcOpus_DecodePlc(
opus_decoder_, plc_buffer, 1)));
// Free memory.
delete[] plc_buffer;
delete[] output_data_decode;
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
}
// Duration estimation.
TEST_P(OpusTest, OpusDurationEstimation) {
PrepareSpeechData(channels_, 20, 20);
// Create.
EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_, channels_));
// 10 ms. We use only first 10 ms of a 20 ms block.
auto speech_block = speech_data_.GetNextBlock();
int encoded_bytes_int = WebRtcOpus_Encode(
opus_encoder_, speech_block.data(),
rtc::CheckedDivExact(speech_block.size(), 2 * channels_),
kMaxBytes, bitstream_);
EXPECT_GE(encoded_bytes_int, 0);
EXPECT_EQ(kOpus10msFrameSamples,
static_cast<size_t>(WebRtcOpus_DurationEst(
opus_decoder_, bitstream_,
static_cast<size_t>(encoded_bytes_int))));
// 20 ms
speech_block = speech_data_.GetNextBlock();
encoded_bytes_int = WebRtcOpus_Encode(
opus_encoder_, speech_block.data(),
rtc::CheckedDivExact(speech_block.size(), channels_),
kMaxBytes, bitstream_);
EXPECT_GE(encoded_bytes_int, 0);
EXPECT_EQ(kOpus20msFrameSamples,
static_cast<size_t>(WebRtcOpus_DurationEst(
opus_decoder_, bitstream_,
static_cast<size_t>(encoded_bytes_int))));
// Free memory.
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
}
TEST_P(OpusTest, OpusDecodeRepacketized) {
constexpr size_t kPackets = 6;
PrepareSpeechData(channels_, 20, 20 * kPackets);
// Create encoder memory.
ASSERT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_,
channels_,
application_));
ASSERT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_,
channels_));
// Set bitrate.
EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_,
channels_ == 1 ? 32000 : 64000));
// Check number of channels for decoder.
EXPECT_EQ(channels_, WebRtcOpus_DecoderChannels(opus_decoder_));
// Encode & decode.
int16_t audio_type;
std::unique_ptr<int16_t[]> output_data_decode(
new int16_t[kPackets * kOpus20msFrameSamples * channels_]);
OpusRepacketizer* rp = opus_repacketizer_create();
size_t num_packets = 0;
constexpr size_t kMaxCycles = 100;
for (size_t idx = 0; idx < kMaxCycles; ++idx) {
auto speech_block = speech_data_.GetNextBlock();
encoded_bytes_ =
WebRtcOpus_Encode(opus_encoder_, speech_block.data(),
rtc::CheckedDivExact(speech_block.size(), channels_),
kMaxBytes, bitstream_);
if (opus_repacketizer_cat(rp, bitstream_, encoded_bytes_) == OPUS_OK) {
++num_packets;
if (num_packets == kPackets) {
break;
}
} else {
// Opus repacketizer cannot guarantee a success. We try again if it fails.
opus_repacketizer_init(rp);
num_packets = 0;
}
}
EXPECT_EQ(kPackets, num_packets);
encoded_bytes_ = opus_repacketizer_out(rp, bitstream_, kMaxBytes);
EXPECT_EQ(kOpus20msFrameSamples * kPackets,
static_cast<size_t>(WebRtcOpus_DurationEst(
opus_decoder_, bitstream_, encoded_bytes_)));
EXPECT_EQ(kOpus20msFrameSamples * kPackets,
static_cast<size_t>(WebRtcOpus_Decode(
opus_decoder_, bitstream_, encoded_bytes_,
output_data_decode.get(), &audio_type)));
// Free memory.
opus_repacketizer_destroy(rp);
EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
}
INSTANTIATE_TEST_CASE_P(VariousMode,
OpusTest,
Combine(Values(1, 2), Values(0, 1)));
} // namespace webrtc