mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-14 06:10:40 +01:00

This is not used and adds a lot of maintenance overhead to the code since it requires that the transport feedback adapter communicates directly with audio send stream. This also means that the packet loss tracker used as input for this can be removed and a lot of wiring up code overall. Bug: webrtc:9883 Change-Id: I25689fb622ed89cbb378c27212a159485f5f53be Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/156502 Commit-Queue: Sebastian Jansson <srte@webrtc.org> Reviewed-by: Ivo Creusen <ivoc@webrtc.org> Reviewed-by: Minyue Li <minyue@webrtc.org> Reviewed-by: Oskar Sundbom <ossu@webrtc.org> Cr-Commit-Position: refs/heads/master@{#29667}
798 lines
33 KiB
C++
798 lines
33 KiB
C++
/*
|
|
* 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 "audio/audio_send_stream.h"
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "api/task_queue/default_task_queue_factory.h"
|
|
#include "api/test/mock_frame_encryptor.h"
|
|
#include "audio/audio_state.h"
|
|
#include "audio/conversion.h"
|
|
#include "audio/mock_voe_channel_proxy.h"
|
|
#include "call/test/mock_rtp_transport_controller_send.h"
|
|
#include "logging/rtc_event_log/mock/mock_rtc_event_log.h"
|
|
#include "modules/audio_device/include/mock_audio_device.h"
|
|
#include "modules/audio_mixer/audio_mixer_impl.h"
|
|
#include "modules/audio_mixer/sine_wave_generator.h"
|
|
#include "modules/audio_processing/include/audio_processing_statistics.h"
|
|
#include "modules/audio_processing/include/mock_audio_processing.h"
|
|
#include "modules/rtp_rtcp/mocks/mock_rtcp_bandwidth_observer.h"
|
|
#include "modules/rtp_rtcp/mocks/mock_rtcp_rtt_stats.h"
|
|
#include "modules/rtp_rtcp/mocks/mock_rtp_rtcp.h"
|
|
#include "rtc_base/task_queue_for_test.h"
|
|
#include "system_wrappers/include/clock.h"
|
|
#include "test/field_trial.h"
|
|
#include "test/gtest.h"
|
|
#include "test/mock_audio_encoder.h"
|
|
#include "test/mock_audio_encoder_factory.h"
|
|
|
|
namespace webrtc {
|
|
namespace test {
|
|
namespace {
|
|
|
|
using ::testing::_;
|
|
using ::testing::AnyNumber;
|
|
using ::testing::Eq;
|
|
using ::testing::Field;
|
|
using ::testing::Invoke;
|
|
using ::testing::Ne;
|
|
using ::testing::Return;
|
|
using ::testing::StrEq;
|
|
|
|
static const float kTolerance = 0.0001f;
|
|
|
|
const uint32_t kSsrc = 1234;
|
|
const char* kCName = "foo_name";
|
|
const int kAudioLevelId = 2;
|
|
const int kTransportSequenceNumberId = 4;
|
|
const int32_t kEchoDelayMedian = 254;
|
|
const int32_t kEchoDelayStdDev = -3;
|
|
const double kDivergentFilterFraction = 0.2f;
|
|
const double kEchoReturnLoss = -65;
|
|
const double kEchoReturnLossEnhancement = 101;
|
|
const double kResidualEchoLikelihood = -1.0f;
|
|
const double kResidualEchoLikelihoodMax = 23.0f;
|
|
const CallSendStatistics kCallStats = {112, 12, 13456, 17890};
|
|
const ReportBlock kReportBlock = {456, 780, 123, 567, 890, 132, 143, 13354};
|
|
const int kTelephoneEventPayloadType = 123;
|
|
const int kTelephoneEventPayloadFrequency = 65432;
|
|
const int kTelephoneEventCode = 45;
|
|
const int kTelephoneEventDuration = 6789;
|
|
constexpr int kIsacPayloadType = 103;
|
|
const SdpAudioFormat kIsacFormat = {"isac", 16000, 1};
|
|
const SdpAudioFormat kOpusFormat = {"opus", 48000, 2};
|
|
const SdpAudioFormat kG722Format = {"g722", 8000, 1};
|
|
const AudioCodecSpec kCodecSpecs[] = {
|
|
{kIsacFormat, {16000, 1, 32000, 10000, 32000}},
|
|
{kOpusFormat, {48000, 1, 32000, 6000, 510000}},
|
|
{kG722Format, {16000, 1, 64000}}};
|
|
|
|
// TODO(dklee): This mirrors calculation in audio_send_stream.cc, which
|
|
// should be made more precise in the future. This can be changed when that
|
|
// logic is more accurate.
|
|
const DataSize kOverheadPerPacket = DataSize::bytes(20 + 8 + 10 + 12);
|
|
const TimeDelta kMinFrameLength = TimeDelta::ms(20);
|
|
const TimeDelta kMaxFrameLength = TimeDelta::ms(120);
|
|
const DataRate kMinOverheadRate = kOverheadPerPacket / kMaxFrameLength;
|
|
const DataRate kMaxOverheadRate = kOverheadPerPacket / kMinFrameLength;
|
|
|
|
class MockLimitObserver : public BitrateAllocator::LimitObserver {
|
|
public:
|
|
MOCK_METHOD1(OnAllocationLimitsChanged, void(BitrateAllocationLimits));
|
|
};
|
|
|
|
std::unique_ptr<MockAudioEncoder> SetupAudioEncoderMock(
|
|
int payload_type,
|
|
const SdpAudioFormat& format) {
|
|
for (const auto& spec : kCodecSpecs) {
|
|
if (format == spec.format) {
|
|
std::unique_ptr<MockAudioEncoder> encoder(
|
|
new ::testing::NiceMock<MockAudioEncoder>());
|
|
ON_CALL(*encoder.get(), SampleRateHz())
|
|
.WillByDefault(Return(spec.info.sample_rate_hz));
|
|
ON_CALL(*encoder.get(), NumChannels())
|
|
.WillByDefault(Return(spec.info.num_channels));
|
|
ON_CALL(*encoder.get(), RtpTimestampRateHz())
|
|
.WillByDefault(Return(spec.format.clockrate_hz));
|
|
ON_CALL(*encoder.get(), GetFrameLengthRange())
|
|
.WillByDefault(Return(absl::optional<std::pair<TimeDelta, TimeDelta>>{
|
|
{TimeDelta::ms(20), TimeDelta::ms(120)}}));
|
|
return encoder;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
rtc::scoped_refptr<MockAudioEncoderFactory> SetupEncoderFactoryMock() {
|
|
rtc::scoped_refptr<MockAudioEncoderFactory> factory =
|
|
new rtc::RefCountedObject<MockAudioEncoderFactory>();
|
|
ON_CALL(*factory.get(), GetSupportedEncoders())
|
|
.WillByDefault(Return(std::vector<AudioCodecSpec>(
|
|
std::begin(kCodecSpecs), std::end(kCodecSpecs))));
|
|
ON_CALL(*factory.get(), QueryAudioEncoder(_))
|
|
.WillByDefault(Invoke(
|
|
[](const SdpAudioFormat& format) -> absl::optional<AudioCodecInfo> {
|
|
for (const auto& spec : kCodecSpecs) {
|
|
if (format == spec.format) {
|
|
return spec.info;
|
|
}
|
|
}
|
|
return absl::nullopt;
|
|
}));
|
|
ON_CALL(*factory.get(), MakeAudioEncoderMock(_, _, _, _))
|
|
.WillByDefault(Invoke([](int payload_type, const SdpAudioFormat& format,
|
|
absl::optional<AudioCodecPairId> codec_pair_id,
|
|
std::unique_ptr<AudioEncoder>* return_value) {
|
|
*return_value = SetupAudioEncoderMock(payload_type, format);
|
|
}));
|
|
return factory;
|
|
}
|
|
|
|
struct ConfigHelper {
|
|
ConfigHelper(bool audio_bwe_enabled, bool expect_set_encoder_call)
|
|
: clock_(1000000),
|
|
task_queue_factory_(CreateDefaultTaskQueueFactory()),
|
|
stream_config_(/*send_transport=*/nullptr, MediaTransportConfig()),
|
|
audio_processing_(new rtc::RefCountedObject<MockAudioProcessing>()),
|
|
bitrate_allocator_(&limit_observer_),
|
|
worker_queue_(task_queue_factory_->CreateTaskQueue(
|
|
"ConfigHelper_worker_queue",
|
|
TaskQueueFactory::Priority::NORMAL)),
|
|
audio_encoder_(nullptr) {
|
|
using ::testing::Invoke;
|
|
|
|
AudioState::Config config;
|
|
config.audio_mixer = AudioMixerImpl::Create();
|
|
config.audio_processing = audio_processing_;
|
|
config.audio_device_module =
|
|
new rtc::RefCountedObject<MockAudioDeviceModule>();
|
|
audio_state_ = AudioState::Create(config);
|
|
|
|
SetupDefaultChannelSend(audio_bwe_enabled);
|
|
SetupMockForSetupSendCodec(expect_set_encoder_call);
|
|
|
|
// Use ISAC as default codec so as to prevent unnecessary |channel_proxy_|
|
|
// calls from the default ctor behavior.
|
|
stream_config_.send_codec_spec =
|
|
AudioSendStream::Config::SendCodecSpec(kIsacPayloadType, kIsacFormat);
|
|
stream_config_.rtp.ssrc = kSsrc;
|
|
stream_config_.rtp.c_name = kCName;
|
|
stream_config_.rtp.extensions.push_back(
|
|
RtpExtension(RtpExtension::kAudioLevelUri, kAudioLevelId));
|
|
if (audio_bwe_enabled) {
|
|
AddBweToConfig(&stream_config_);
|
|
}
|
|
stream_config_.encoder_factory = SetupEncoderFactoryMock();
|
|
stream_config_.min_bitrate_bps = 10000;
|
|
stream_config_.max_bitrate_bps = 65000;
|
|
}
|
|
|
|
std::unique_ptr<internal::AudioSendStream> CreateAudioSendStream() {
|
|
EXPECT_CALL(rtp_transport_, GetWorkerQueue())
|
|
.WillRepeatedly(Return(&worker_queue_));
|
|
return std::unique_ptr<internal::AudioSendStream>(
|
|
new internal::AudioSendStream(
|
|
Clock::GetRealTimeClock(), stream_config_, audio_state_,
|
|
task_queue_factory_.get(), &rtp_transport_, &bitrate_allocator_,
|
|
&event_log_, &rtcp_rtt_stats_, absl::nullopt,
|
|
std::unique_ptr<voe::ChannelSendInterface>(channel_send_)));
|
|
}
|
|
|
|
AudioSendStream::Config& config() { return stream_config_; }
|
|
MockAudioEncoderFactory& mock_encoder_factory() {
|
|
return *static_cast<MockAudioEncoderFactory*>(
|
|
stream_config_.encoder_factory.get());
|
|
}
|
|
MockChannelSend* channel_send() { return channel_send_; }
|
|
RtpTransportControllerSendInterface* transport() { return &rtp_transport_; }
|
|
|
|
static void AddBweToConfig(AudioSendStream::Config* config) {
|
|
config->rtp.extensions.push_back(RtpExtension(
|
|
RtpExtension::kTransportSequenceNumberUri, kTransportSequenceNumberId));
|
|
config->send_codec_spec->transport_cc_enabled = true;
|
|
}
|
|
|
|
void SetupDefaultChannelSend(bool audio_bwe_enabled) {
|
|
EXPECT_TRUE(channel_send_ == nullptr);
|
|
channel_send_ = new ::testing::StrictMock<MockChannelSend>();
|
|
EXPECT_CALL(*channel_send_, GetRtpRtcp()).WillRepeatedly(Invoke([this]() {
|
|
return &this->rtp_rtcp_;
|
|
}));
|
|
EXPECT_CALL(rtp_rtcp_, SSRC).WillRepeatedly(Return(kSsrc));
|
|
EXPECT_CALL(*channel_send_, SetRTCP_CNAME(StrEq(kCName))).Times(1);
|
|
EXPECT_CALL(*channel_send_, SetFrameEncryptor(_)).Times(1);
|
|
EXPECT_CALL(*channel_send_, SetExtmapAllowMixed(false)).Times(1);
|
|
EXPECT_CALL(*channel_send_,
|
|
SetSendAudioLevelIndicationStatus(true, kAudioLevelId))
|
|
.Times(1);
|
|
EXPECT_CALL(rtp_transport_, GetBandwidthObserver())
|
|
.WillRepeatedly(Return(&bandwidth_observer_));
|
|
if (audio_bwe_enabled) {
|
|
EXPECT_CALL(*channel_send_,
|
|
EnableSendTransportSequenceNumber(kTransportSequenceNumberId))
|
|
.Times(1);
|
|
EXPECT_CALL(*channel_send_,
|
|
RegisterSenderCongestionControlObjects(
|
|
&rtp_transport_, Eq(&bandwidth_observer_)))
|
|
.Times(1);
|
|
} else {
|
|
EXPECT_CALL(*channel_send_, RegisterSenderCongestionControlObjects(
|
|
&rtp_transport_, Eq(nullptr)))
|
|
.Times(1);
|
|
}
|
|
EXPECT_CALL(*channel_send_, ResetSenderCongestionControlObjects()).Times(1);
|
|
EXPECT_CALL(*channel_send_, SetRid(std::string(), 0, 0)).Times(1);
|
|
}
|
|
|
|
void SetupMockForSetupSendCodec(bool expect_set_encoder_call) {
|
|
if (expect_set_encoder_call) {
|
|
EXPECT_CALL(*channel_send_, SetEncoderForMock(_, _))
|
|
.WillOnce(Invoke(
|
|
[this](int payload_type, std::unique_ptr<AudioEncoder>* encoder) {
|
|
this->audio_encoder_ = std::move(*encoder);
|
|
return true;
|
|
}));
|
|
}
|
|
}
|
|
|
|
void SetupMockForCallEncoder() {
|
|
// Let ModifyEncoder to invoke mock audio encoder.
|
|
EXPECT_CALL(*channel_send_, CallEncoder(_))
|
|
.WillRepeatedly(
|
|
[this](rtc::FunctionView<void(AudioEncoder*)> modifier) {
|
|
if (this->audio_encoder_)
|
|
modifier(this->audio_encoder_.get());
|
|
});
|
|
}
|
|
|
|
void SetupMockForSendTelephoneEvent() {
|
|
EXPECT_TRUE(channel_send_);
|
|
EXPECT_CALL(*channel_send_, SetSendTelephoneEventPayloadType(
|
|
kTelephoneEventPayloadType,
|
|
kTelephoneEventPayloadFrequency));
|
|
EXPECT_CALL(
|
|
*channel_send_,
|
|
SendTelephoneEventOutband(kTelephoneEventCode, kTelephoneEventDuration))
|
|
.WillOnce(Return(true));
|
|
}
|
|
|
|
void SetupMockForGetStats() {
|
|
using ::testing::DoAll;
|
|
using ::testing::SetArgPointee;
|
|
using ::testing::SetArgReferee;
|
|
|
|
std::vector<ReportBlock> report_blocks;
|
|
webrtc::ReportBlock block = kReportBlock;
|
|
report_blocks.push_back(block); // Has wrong SSRC.
|
|
block.source_SSRC = kSsrc;
|
|
report_blocks.push_back(block); // Correct block.
|
|
block.fraction_lost = 0;
|
|
report_blocks.push_back(block); // Duplicate SSRC, bad fraction_lost.
|
|
|
|
EXPECT_TRUE(channel_send_);
|
|
EXPECT_CALL(*channel_send_, GetRTCPStatistics())
|
|
.WillRepeatedly(Return(kCallStats));
|
|
EXPECT_CALL(*channel_send_, GetRemoteRTCPReportBlocks())
|
|
.WillRepeatedly(Return(report_blocks));
|
|
EXPECT_CALL(*channel_send_, GetANAStatistics())
|
|
.WillRepeatedly(Return(ANAStats()));
|
|
EXPECT_CALL(*channel_send_, GetBitrate()).WillRepeatedly(Return(0));
|
|
|
|
audio_processing_stats_.echo_return_loss = kEchoReturnLoss;
|
|
audio_processing_stats_.echo_return_loss_enhancement =
|
|
kEchoReturnLossEnhancement;
|
|
audio_processing_stats_.delay_median_ms = kEchoDelayMedian;
|
|
audio_processing_stats_.delay_standard_deviation_ms = kEchoDelayStdDev;
|
|
audio_processing_stats_.divergent_filter_fraction =
|
|
kDivergentFilterFraction;
|
|
audio_processing_stats_.residual_echo_likelihood = kResidualEchoLikelihood;
|
|
audio_processing_stats_.residual_echo_likelihood_recent_max =
|
|
kResidualEchoLikelihoodMax;
|
|
|
|
EXPECT_CALL(*audio_processing_, GetStatistics(true))
|
|
.WillRepeatedly(Return(audio_processing_stats_));
|
|
}
|
|
TaskQueueForTest* worker() { return &worker_queue_; }
|
|
|
|
private:
|
|
SimulatedClock clock_;
|
|
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
|
|
rtc::scoped_refptr<AudioState> audio_state_;
|
|
AudioSendStream::Config stream_config_;
|
|
::testing::StrictMock<MockChannelSend>* channel_send_ = nullptr;
|
|
rtc::scoped_refptr<MockAudioProcessing> audio_processing_;
|
|
AudioProcessingStats audio_processing_stats_;
|
|
::testing::StrictMock<MockRtcpBandwidthObserver> bandwidth_observer_;
|
|
::testing::NiceMock<MockRtcEventLog> event_log_;
|
|
::testing::NiceMock<MockRtpTransportControllerSend> rtp_transport_;
|
|
::testing::NiceMock<MockRtpRtcp> rtp_rtcp_;
|
|
MockRtcpRttStats rtcp_rtt_stats_;
|
|
::testing::NiceMock<MockLimitObserver> limit_observer_;
|
|
BitrateAllocator bitrate_allocator_;
|
|
// |worker_queue| is defined last to ensure all pending tasks are cancelled
|
|
// and deleted before any other members.
|
|
TaskQueueForTest worker_queue_;
|
|
std::unique_ptr<AudioEncoder> audio_encoder_;
|
|
};
|
|
|
|
// The audio level ranges linearly [0,32767].
|
|
std::unique_ptr<AudioFrame> CreateAudioFrame1kHzSineWave(int16_t audio_level,
|
|
int duration_ms,
|
|
int sample_rate_hz,
|
|
size_t num_channels) {
|
|
size_t samples_per_channel = sample_rate_hz / (1000 / duration_ms);
|
|
std::vector<int16_t> audio_data(samples_per_channel * num_channels, 0);
|
|
std::unique_ptr<AudioFrame> audio_frame = std::make_unique<AudioFrame>();
|
|
audio_frame->UpdateFrame(0 /* RTP timestamp */, &audio_data[0],
|
|
samples_per_channel, sample_rate_hz,
|
|
AudioFrame::SpeechType::kNormalSpeech,
|
|
AudioFrame::VADActivity::kVadUnknown, num_channels);
|
|
SineWaveGenerator wave_generator(1000.0, audio_level);
|
|
wave_generator.GenerateNextFrame(audio_frame.get());
|
|
return audio_frame;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(AudioSendStreamTest, ConfigToString) {
|
|
AudioSendStream::Config config(/*send_transport=*/nullptr,
|
|
MediaTransportConfig());
|
|
config.rtp.ssrc = kSsrc;
|
|
config.rtp.c_name = kCName;
|
|
config.min_bitrate_bps = 12000;
|
|
config.max_bitrate_bps = 34000;
|
|
config.send_codec_spec =
|
|
AudioSendStream::Config::SendCodecSpec(kIsacPayloadType, kIsacFormat);
|
|
config.send_codec_spec->nack_enabled = true;
|
|
config.send_codec_spec->transport_cc_enabled = false;
|
|
config.send_codec_spec->cng_payload_type = 42;
|
|
config.encoder_factory = MockAudioEncoderFactory::CreateUnusedFactory();
|
|
config.rtp.extmap_allow_mixed = true;
|
|
config.rtp.extensions.push_back(
|
|
RtpExtension(RtpExtension::kAudioLevelUri, kAudioLevelId));
|
|
config.rtcp_report_interval_ms = 2500;
|
|
EXPECT_EQ(
|
|
"{rtp: {ssrc: 1234, extmap-allow-mixed: true, extensions: [{uri: "
|
|
"urn:ietf:params:rtp-hdrext:ssrc-audio-level, id: 2}], "
|
|
"c_name: foo_name}, rtcp_report_interval_ms: 2500, "
|
|
"send_transport: null, media_transport_config: {media_transport: null}, "
|
|
"min_bitrate_bps: 12000, max_bitrate_bps: 34000, "
|
|
"send_codec_spec: {nack_enabled: true, transport_cc_enabled: false, "
|
|
"cng_payload_type: 42, payload_type: 103, "
|
|
"format: {name: isac, clockrate_hz: 16000, num_channels: 1, "
|
|
"parameters: {}}}}",
|
|
config.ToString());
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, ConstructDestruct) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, SendTelephoneEvent) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
helper.SetupMockForSendTelephoneEvent();
|
|
EXPECT_TRUE(send_stream->SendTelephoneEvent(
|
|
kTelephoneEventPayloadType, kTelephoneEventPayloadFrequency,
|
|
kTelephoneEventCode, kTelephoneEventDuration));
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, SetMuted) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
EXPECT_CALL(*helper.channel_send(), SetInputMute(true));
|
|
send_stream->SetMuted(true);
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, AudioBweCorrectObjectsOnChannelProxy) {
|
|
ScopedFieldTrials field_trials("WebRTC-Audio-SendSideBwe/Enabled/");
|
|
ConfigHelper helper(true, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, NoAudioBweCorrectObjectsOnChannelProxy) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, GetStats) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
helper.SetupMockForGetStats();
|
|
AudioSendStream::Stats stats = send_stream->GetStats(true);
|
|
EXPECT_EQ(kSsrc, stats.local_ssrc);
|
|
EXPECT_EQ(kCallStats.payload_bytes_sent, stats.payload_bytes_sent);
|
|
EXPECT_EQ(kCallStats.header_and_padding_bytes_sent,
|
|
stats.header_and_padding_bytes_sent);
|
|
EXPECT_EQ(kCallStats.packetsSent, stats.packets_sent);
|
|
EXPECT_EQ(kReportBlock.cumulative_num_packets_lost, stats.packets_lost);
|
|
EXPECT_EQ(Q8ToFloat(kReportBlock.fraction_lost), stats.fraction_lost);
|
|
EXPECT_EQ(kIsacFormat.name, stats.codec_name);
|
|
EXPECT_EQ(static_cast<int32_t>(kReportBlock.interarrival_jitter /
|
|
(kIsacFormat.clockrate_hz / 1000)),
|
|
stats.jitter_ms);
|
|
EXPECT_EQ(kCallStats.rttMs, stats.rtt_ms);
|
|
EXPECT_EQ(0, stats.audio_level);
|
|
EXPECT_EQ(0, stats.total_input_energy);
|
|
EXPECT_EQ(0, stats.total_input_duration);
|
|
EXPECT_EQ(kEchoDelayMedian, stats.apm_statistics.delay_median_ms);
|
|
EXPECT_EQ(kEchoDelayStdDev, stats.apm_statistics.delay_standard_deviation_ms);
|
|
EXPECT_EQ(kEchoReturnLoss, stats.apm_statistics.echo_return_loss);
|
|
EXPECT_EQ(kEchoReturnLossEnhancement,
|
|
stats.apm_statistics.echo_return_loss_enhancement);
|
|
EXPECT_EQ(kDivergentFilterFraction,
|
|
stats.apm_statistics.divergent_filter_fraction);
|
|
EXPECT_EQ(kResidualEchoLikelihood,
|
|
stats.apm_statistics.residual_echo_likelihood);
|
|
EXPECT_EQ(kResidualEchoLikelihoodMax,
|
|
stats.apm_statistics.residual_echo_likelihood_recent_max);
|
|
EXPECT_FALSE(stats.typing_noise_detected);
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, GetStatsAudioLevel) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
helper.SetupMockForGetStats();
|
|
EXPECT_CALL(*helper.channel_send(), ProcessAndEncodeAudioForMock(_))
|
|
.Times(AnyNumber());
|
|
|
|
constexpr int kSampleRateHz = 48000;
|
|
constexpr size_t kNumChannels = 1;
|
|
|
|
constexpr int16_t kSilentAudioLevel = 0;
|
|
constexpr int16_t kMaxAudioLevel = 32767; // Audio level is [0,32767].
|
|
constexpr int kAudioFrameDurationMs = 10;
|
|
|
|
// Process 10 audio frames (100 ms) of silence. After this, on the next
|
|
// (11-th) frame, the audio level will be updated with the maximum audio level
|
|
// of the first 11 frames. See AudioLevel.
|
|
for (size_t i = 0; i < 10; ++i) {
|
|
send_stream->SendAudioData(CreateAudioFrame1kHzSineWave(
|
|
kSilentAudioLevel, kAudioFrameDurationMs, kSampleRateHz, kNumChannels));
|
|
}
|
|
AudioSendStream::Stats stats = send_stream->GetStats();
|
|
EXPECT_EQ(kSilentAudioLevel, stats.audio_level);
|
|
EXPECT_NEAR(0.0f, stats.total_input_energy, kTolerance);
|
|
EXPECT_NEAR(0.1f, stats.total_input_duration, kTolerance); // 100 ms = 0.1 s
|
|
|
|
// Process 10 audio frames (100 ms) of maximum audio level.
|
|
// Note that AudioLevel updates the audio level every 11th frame, processing
|
|
// 10 frames above was needed to see a non-zero audio level here.
|
|
for (size_t i = 0; i < 10; ++i) {
|
|
send_stream->SendAudioData(CreateAudioFrame1kHzSineWave(
|
|
kMaxAudioLevel, kAudioFrameDurationMs, kSampleRateHz, kNumChannels));
|
|
}
|
|
stats = send_stream->GetStats();
|
|
EXPECT_EQ(kMaxAudioLevel, stats.audio_level);
|
|
// Energy increases by energy*duration, where energy is audio level in [0,1].
|
|
EXPECT_NEAR(0.1f, stats.total_input_energy, kTolerance); // 0.1 s of max
|
|
EXPECT_NEAR(0.2f, stats.total_input_duration, kTolerance); // 200 ms = 0.2 s
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, SendCodecAppliesAudioNetworkAdaptor) {
|
|
ConfigHelper helper(false, true);
|
|
helper.config().send_codec_spec =
|
|
AudioSendStream::Config::SendCodecSpec(0, kOpusFormat);
|
|
const std::string kAnaConfigString = "abcde";
|
|
const std::string kAnaReconfigString = "12345";
|
|
|
|
helper.config().audio_network_adaptor_config = kAnaConfigString;
|
|
|
|
EXPECT_CALL(helper.mock_encoder_factory(), MakeAudioEncoderMock(_, _, _, _))
|
|
.WillOnce(Invoke([&kAnaConfigString, &kAnaReconfigString](
|
|
int payload_type, const SdpAudioFormat& format,
|
|
absl::optional<AudioCodecPairId> codec_pair_id,
|
|
std::unique_ptr<AudioEncoder>* return_value) {
|
|
auto mock_encoder = SetupAudioEncoderMock(payload_type, format);
|
|
EXPECT_CALL(*mock_encoder,
|
|
EnableAudioNetworkAdaptor(StrEq(kAnaConfigString), _))
|
|
.WillOnce(Return(true));
|
|
EXPECT_CALL(*mock_encoder,
|
|
EnableAudioNetworkAdaptor(StrEq(kAnaReconfigString), _))
|
|
.WillOnce(Return(true));
|
|
*return_value = std::move(mock_encoder);
|
|
}));
|
|
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
|
|
auto stream_config = helper.config();
|
|
stream_config.audio_network_adaptor_config = kAnaReconfigString;
|
|
|
|
helper.SetupMockForCallEncoder();
|
|
send_stream->Reconfigure(stream_config);
|
|
}
|
|
|
|
// VAD is applied when codec is mono and the CNG frequency matches the codec
|
|
// clock rate.
|
|
TEST(AudioSendStreamTest, SendCodecCanApplyVad) {
|
|
ConfigHelper helper(false, false);
|
|
helper.config().send_codec_spec =
|
|
AudioSendStream::Config::SendCodecSpec(9, kG722Format);
|
|
helper.config().send_codec_spec->cng_payload_type = 105;
|
|
using ::testing::Invoke;
|
|
std::unique_ptr<AudioEncoder> stolen_encoder;
|
|
EXPECT_CALL(*helper.channel_send(), SetEncoderForMock(_, _))
|
|
.WillOnce(
|
|
Invoke([&stolen_encoder](int payload_type,
|
|
std::unique_ptr<AudioEncoder>* encoder) {
|
|
stolen_encoder = std::move(*encoder);
|
|
return true;
|
|
}));
|
|
EXPECT_CALL(*helper.channel_send(), RegisterCngPayloadType(105, 8000));
|
|
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
|
|
// We cannot truly determine if the encoder created is an AudioEncoderCng. It
|
|
// is the only reasonable implementation that will return something from
|
|
// ReclaimContainedEncoders, though.
|
|
ASSERT_TRUE(stolen_encoder);
|
|
EXPECT_FALSE(stolen_encoder->ReclaimContainedEncoders().empty());
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, DoesNotPassHigherBitrateThanMaxBitrate) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
EXPECT_CALL(*helper.channel_send(),
|
|
OnBitrateAllocation(
|
|
Field(&BitrateAllocationUpdate::target_bitrate,
|
|
Eq(DataRate::bps(helper.config().max_bitrate_bps)))));
|
|
BitrateAllocationUpdate update;
|
|
update.target_bitrate = DataRate::bps(helper.config().max_bitrate_bps + 5000);
|
|
update.packet_loss_ratio = 0;
|
|
update.round_trip_time = TimeDelta::ms(50);
|
|
update.bwe_period = TimeDelta::ms(6000);
|
|
helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); },
|
|
RTC_FROM_HERE);
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, SSBweTargetInRangeRespected) {
|
|
ScopedFieldTrials field_trials("WebRTC-Audio-SendSideBwe/Enabled/");
|
|
ConfigHelper helper(true, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
EXPECT_CALL(*helper.channel_send(),
|
|
OnBitrateAllocation(Field(
|
|
&BitrateAllocationUpdate::target_bitrate,
|
|
Eq(DataRate::bps(helper.config().max_bitrate_bps - 5000)))));
|
|
BitrateAllocationUpdate update;
|
|
update.target_bitrate = DataRate::bps(helper.config().max_bitrate_bps - 5000);
|
|
helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); },
|
|
RTC_FROM_HERE);
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, SSBweFieldTrialMinRespected) {
|
|
ScopedFieldTrials field_trials(
|
|
"WebRTC-Audio-SendSideBwe/Enabled/"
|
|
"WebRTC-Audio-Allocation/min:6kbps,max:64kbps/");
|
|
ConfigHelper helper(true, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
EXPECT_CALL(
|
|
*helper.channel_send(),
|
|
OnBitrateAllocation(Field(&BitrateAllocationUpdate::target_bitrate,
|
|
Eq(DataRate::kbps(6)))));
|
|
BitrateAllocationUpdate update;
|
|
update.target_bitrate = DataRate::kbps(1);
|
|
helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); },
|
|
RTC_FROM_HERE);
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, SSBweFieldTrialMaxRespected) {
|
|
ScopedFieldTrials field_trials(
|
|
"WebRTC-Audio-SendSideBwe/Enabled/"
|
|
"WebRTC-Audio-Allocation/min:6kbps,max:64kbps/");
|
|
ConfigHelper helper(true, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
EXPECT_CALL(
|
|
*helper.channel_send(),
|
|
OnBitrateAllocation(Field(&BitrateAllocationUpdate::target_bitrate,
|
|
Eq(DataRate::kbps(64)))));
|
|
BitrateAllocationUpdate update;
|
|
update.target_bitrate = DataRate::kbps(128);
|
|
helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); },
|
|
RTC_FROM_HERE);
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, SSBweWithOverhead) {
|
|
ScopedFieldTrials field_trials(
|
|
"WebRTC-Audio-SendSideBwe/Enabled/"
|
|
"WebRTC-SendSideBwe-WithOverhead/Enabled/"
|
|
"WebRTC-Audio-LegacyOverhead/Disabled/");
|
|
ConfigHelper helper(true, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
EXPECT_CALL(*helper.channel_send(), CallEncoder(_)).Times(1);
|
|
send_stream->OnOverheadChanged(kOverheadPerPacket.bytes<size_t>());
|
|
const DataRate bitrate =
|
|
DataRate::bps(helper.config().max_bitrate_bps) + kMaxOverheadRate;
|
|
EXPECT_CALL(*helper.channel_send(),
|
|
OnBitrateAllocation(Field(
|
|
&BitrateAllocationUpdate::target_bitrate, Eq(bitrate))));
|
|
BitrateAllocationUpdate update;
|
|
update.target_bitrate = bitrate;
|
|
helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); },
|
|
RTC_FROM_HERE);
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, SSBweWithOverheadMinRespected) {
|
|
ScopedFieldTrials field_trials(
|
|
"WebRTC-Audio-SendSideBwe/Enabled/"
|
|
"WebRTC-SendSideBwe-WithOverhead/Enabled/"
|
|
"WebRTC-Audio-LegacyOverhead/Disabled/"
|
|
"WebRTC-Audio-Allocation/min:6kbps,max:64kbps/");
|
|
ConfigHelper helper(true, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
EXPECT_CALL(*helper.channel_send(), CallEncoder(_)).Times(1);
|
|
send_stream->OnOverheadChanged(kOverheadPerPacket.bytes<size_t>());
|
|
const DataRate bitrate = DataRate::kbps(6) + kMinOverheadRate;
|
|
EXPECT_CALL(*helper.channel_send(),
|
|
OnBitrateAllocation(Field(
|
|
&BitrateAllocationUpdate::target_bitrate, Eq(bitrate))));
|
|
BitrateAllocationUpdate update;
|
|
update.target_bitrate = DataRate::kbps(1);
|
|
helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); },
|
|
RTC_FROM_HERE);
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, SSBweWithOverheadMaxRespected) {
|
|
ScopedFieldTrials field_trials(
|
|
"WebRTC-Audio-SendSideBwe/Enabled/"
|
|
"WebRTC-SendSideBwe-WithOverhead/Enabled/"
|
|
"WebRTC-Audio-LegacyOverhead/Disabled/"
|
|
"WebRTC-Audio-Allocation/min:6kbps,max:64kbps/");
|
|
ConfigHelper helper(true, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
EXPECT_CALL(*helper.channel_send(), CallEncoder(_)).Times(1);
|
|
send_stream->OnOverheadChanged(kOverheadPerPacket.bytes<size_t>());
|
|
const DataRate bitrate = DataRate::kbps(64) + kMaxOverheadRate;
|
|
EXPECT_CALL(*helper.channel_send(),
|
|
OnBitrateAllocation(Field(
|
|
&BitrateAllocationUpdate::target_bitrate, Eq(bitrate))));
|
|
BitrateAllocationUpdate update;
|
|
update.target_bitrate = DataRate::kbps(128);
|
|
helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); },
|
|
RTC_FROM_HERE);
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, ProbingIntervalOnBitrateUpdated) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
|
|
EXPECT_CALL(*helper.channel_send(),
|
|
OnBitrateAllocation(Field(&BitrateAllocationUpdate::bwe_period,
|
|
Eq(TimeDelta::ms(5000)))));
|
|
BitrateAllocationUpdate update;
|
|
update.target_bitrate = DataRate::bps(helper.config().max_bitrate_bps + 5000);
|
|
update.packet_loss_ratio = 0;
|
|
update.round_trip_time = TimeDelta::ms(50);
|
|
update.bwe_period = TimeDelta::ms(5000);
|
|
helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); },
|
|
RTC_FROM_HERE);
|
|
}
|
|
|
|
// Test that AudioSendStream doesn't recreate the encoder unnecessarily.
|
|
TEST(AudioSendStreamTest, DontRecreateEncoder) {
|
|
ConfigHelper helper(false, false);
|
|
// WillOnce is (currently) the default used by ConfigHelper if asked to set an
|
|
// expectation for SetEncoder. Since this behavior is essential for this test
|
|
// to be correct, it's instead set-up manually here. Otherwise a simple change
|
|
// to ConfigHelper (say to WillRepeatedly) would silently make this test
|
|
// useless.
|
|
EXPECT_CALL(*helper.channel_send(), SetEncoderForMock(_, _))
|
|
.WillOnce(Return());
|
|
|
|
EXPECT_CALL(*helper.channel_send(), RegisterCngPayloadType(105, 8000));
|
|
|
|
helper.config().send_codec_spec =
|
|
AudioSendStream::Config::SendCodecSpec(9, kG722Format);
|
|
helper.config().send_codec_spec->cng_payload_type = 105;
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
send_stream->Reconfigure(helper.config());
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, ReconfigureTransportCcResetsFirst) {
|
|
ScopedFieldTrials field_trials("WebRTC-Audio-SendSideBwe/Enabled/");
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
auto new_config = helper.config();
|
|
ConfigHelper::AddBweToConfig(&new_config);
|
|
EXPECT_CALL(*helper.channel_send(),
|
|
EnableSendTransportSequenceNumber(kTransportSequenceNumberId))
|
|
.Times(1);
|
|
{
|
|
::testing::InSequence seq;
|
|
EXPECT_CALL(*helper.channel_send(), ResetSenderCongestionControlObjects())
|
|
.Times(1);
|
|
EXPECT_CALL(*helper.channel_send(), RegisterSenderCongestionControlObjects(
|
|
helper.transport(), Ne(nullptr)))
|
|
.Times(1);
|
|
}
|
|
|
|
send_stream->Reconfigure(new_config);
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, OnTransportOverheadChanged) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
auto new_config = helper.config();
|
|
|
|
// CallEncoder will be called on overhead change.
|
|
EXPECT_CALL(*helper.channel_send(), CallEncoder(::testing::_)).Times(1);
|
|
|
|
const size_t transport_overhead_per_packet_bytes = 333;
|
|
send_stream->SetTransportOverhead(transport_overhead_per_packet_bytes);
|
|
|
|
EXPECT_EQ(transport_overhead_per_packet_bytes,
|
|
send_stream->TestOnlyGetPerPacketOverheadBytes());
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, OnAudioOverheadChanged) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
auto new_config = helper.config();
|
|
|
|
// CallEncoder will be called on overhead change.
|
|
EXPECT_CALL(*helper.channel_send(), CallEncoder(::testing::_)).Times(1);
|
|
|
|
const size_t audio_overhead_per_packet_bytes = 555;
|
|
send_stream->OnOverheadChanged(audio_overhead_per_packet_bytes);
|
|
EXPECT_EQ(audio_overhead_per_packet_bytes,
|
|
send_stream->TestOnlyGetPerPacketOverheadBytes());
|
|
}
|
|
|
|
TEST(AudioSendStreamTest, OnAudioAndTransportOverheadChanged) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
auto new_config = helper.config();
|
|
|
|
// CallEncoder will be called when each of overhead changes.
|
|
EXPECT_CALL(*helper.channel_send(), CallEncoder(::testing::_)).Times(2);
|
|
|
|
const size_t transport_overhead_per_packet_bytes = 333;
|
|
send_stream->SetTransportOverhead(transport_overhead_per_packet_bytes);
|
|
|
|
const size_t audio_overhead_per_packet_bytes = 555;
|
|
send_stream->OnOverheadChanged(audio_overhead_per_packet_bytes);
|
|
|
|
EXPECT_EQ(
|
|
transport_overhead_per_packet_bytes + audio_overhead_per_packet_bytes,
|
|
send_stream->TestOnlyGetPerPacketOverheadBytes());
|
|
}
|
|
|
|
// Validates that reconfiguring the AudioSendStream with a Frame encryptor
|
|
// correctly reconfigures on the object without crashing.
|
|
TEST(AudioSendStreamTest, ReconfigureWithFrameEncryptor) {
|
|
ConfigHelper helper(false, true);
|
|
auto send_stream = helper.CreateAudioSendStream();
|
|
auto new_config = helper.config();
|
|
|
|
rtc::scoped_refptr<FrameEncryptorInterface> mock_frame_encryptor_0(
|
|
new rtc::RefCountedObject<MockFrameEncryptor>());
|
|
new_config.frame_encryptor = mock_frame_encryptor_0;
|
|
EXPECT_CALL(*helper.channel_send(), SetFrameEncryptor(Ne(nullptr))).Times(1);
|
|
send_stream->Reconfigure(new_config);
|
|
|
|
// Not updating the frame encryptor shouldn't force it to reconfigure.
|
|
EXPECT_CALL(*helper.channel_send(), SetFrameEncryptor(_)).Times(0);
|
|
send_stream->Reconfigure(new_config);
|
|
|
|
// Updating frame encryptor to a new object should force a call to the proxy.
|
|
rtc::scoped_refptr<FrameEncryptorInterface> mock_frame_encryptor_1(
|
|
new rtc::RefCountedObject<MockFrameEncryptor>());
|
|
new_config.frame_encryptor = mock_frame_encryptor_1;
|
|
new_config.crypto_options.sframe.require_frame_encryption = true;
|
|
EXPECT_CALL(*helper.channel_send(), SetFrameEncryptor(Ne(nullptr))).Times(1);
|
|
send_stream->Reconfigure(new_config);
|
|
}
|
|
} // namespace test
|
|
} // namespace webrtc
|