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:
committed by
Commit Bot
parent
6674846b4a
commit
bb547203bf
168
modules/audio_coding/codecs/opus/audio_decoder_opus.cc
Normal file
168
modules/audio_coding/codecs/opus/audio_decoder_opus.cc
Normal 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
|
||||
55
modules/audio_coding/codecs/opus/audio_decoder_opus.h
Normal file
55
modules/audio_coding/codecs/opus/audio_decoder_opus.h
Normal 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_
|
||||
783
modules/audio_coding/codecs/opus/audio_encoder_opus.cc
Normal file
783
modules/audio_coding/codecs/opus/audio_encoder_opus.cc
Normal 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
|
||||
182
modules/audio_coding/codecs/opus/audio_encoder_opus.h
Normal file
182
modules/audio_coding/codecs/opus/audio_encoder_opus.h
Normal 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_
|
||||
763
modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc
Normal file
763
modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc
Normal 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
|
||||
98
modules/audio_coding/codecs/opus/opus_complexity_unittest.cc
Normal file
98
modules/audio_coding/codecs/opus/opus_complexity_unittest.cc
Normal 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
|
||||
242
modules/audio_coding/codecs/opus/opus_fec_test.cc
Normal file
242
modules/audio_coding/codecs/opus/opus_fec_test.cc
Normal 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
|
||||
36
modules/audio_coding/codecs/opus/opus_inst.h
Normal file
36
modules/audio_coding/codecs/opus/opus_inst.h
Normal 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_
|
||||
503
modules/audio_coding/codecs/opus/opus_interface.c
Normal file
503
modules/audio_coding/codecs/opus/opus_interface.c
Normal 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;
|
||||
}
|
||||
397
modules/audio_coding/codecs/opus/opus_interface.h
Normal file
397
modules/audio_coding/codecs/opus/opus_interface.h
Normal 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_
|
||||
121
modules/audio_coding/codecs/opus/opus_speed_test.cc
Normal file
121
modules/audio_coding/codecs/opus/opus_speed_test.cc
Normal 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
|
||||
774
modules/audio_coding/codecs/opus/opus_unittest.cc
Normal file
774
modules/audio_coding/codecs/opus/opus_unittest.cc
Normal 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
|
||||
Reference in New Issue
Block a user