mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-12 21:30:45 +01:00

that rtc::Location parameter was used only as extra information for the RTC_CHECKs directly in the function, thus call stack of the crash should provide all the information about the caller. Bug: webrtc:11318 Change-Id: Iec6dd2c5de547f3e1601647a614be7ce57a55734 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/270920 Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org> Commit-Queue: Danil Chapovalov <danilchap@webrtc.org> Cr-Commit-Position: refs/heads/main@{#37748}
794 lines
28 KiB
C++
794 lines
28 KiB
C++
/*
|
|
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "modules/audio_mixer/audio_mixer_impl.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/types/optional.h"
|
|
#include "api/audio/audio_mixer.h"
|
|
#include "api/rtp_packet_info.h"
|
|
#include "api/rtp_packet_infos.h"
|
|
#include "api/units/timestamp.h"
|
|
#include "modules/audio_mixer/default_output_rate_calculator.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/strings/string_builder.h"
|
|
#include "rtc_base/task_queue_for_test.h"
|
|
#include "test/gmock.h"
|
|
#include "test/gtest.h"
|
|
|
|
using ::testing::_;
|
|
using ::testing::Exactly;
|
|
using ::testing::Invoke;
|
|
using ::testing::Return;
|
|
using ::testing::UnorderedElementsAre;
|
|
|
|
namespace webrtc {
|
|
|
|
namespace {
|
|
|
|
constexpr int kDefaultSampleRateHz = 48000;
|
|
|
|
// Utility function that resets the frame member variables with
|
|
// sensible defaults.
|
|
void ResetFrame(AudioFrame* frame) {
|
|
frame->sample_rate_hz_ = kDefaultSampleRateHz;
|
|
frame->num_channels_ = 1;
|
|
|
|
// Frame duration 10ms.
|
|
frame->samples_per_channel_ = kDefaultSampleRateHz / 100;
|
|
frame->vad_activity_ = AudioFrame::kVadActive;
|
|
frame->speech_type_ = AudioFrame::kNormalSpeech;
|
|
}
|
|
|
|
std::string ProduceDebugText(int sample_rate_hz,
|
|
int number_of_channels,
|
|
int number_of_sources) {
|
|
rtc::StringBuilder ss;
|
|
ss << "Sample rate: " << sample_rate_hz << " ";
|
|
ss << "Number of channels: " << number_of_channels << " ";
|
|
ss << "Number of sources: " << number_of_sources;
|
|
return ss.Release();
|
|
}
|
|
|
|
AudioFrame frame_for_mixing;
|
|
|
|
} // namespace
|
|
|
|
class MockMixerAudioSource : public ::testing::NiceMock<AudioMixer::Source> {
|
|
public:
|
|
MockMixerAudioSource()
|
|
: fake_audio_frame_info_(AudioMixer::Source::AudioFrameInfo::kNormal) {
|
|
ON_CALL(*this, GetAudioFrameWithInfo(_, _))
|
|
.WillByDefault(
|
|
Invoke(this, &MockMixerAudioSource::FakeAudioFrameWithInfo));
|
|
ON_CALL(*this, PreferredSampleRate())
|
|
.WillByDefault(Return(kDefaultSampleRateHz));
|
|
}
|
|
|
|
MOCK_METHOD(AudioFrameInfo,
|
|
GetAudioFrameWithInfo,
|
|
(int sample_rate_hz, AudioFrame* audio_frame),
|
|
(override));
|
|
|
|
MOCK_METHOD(int, PreferredSampleRate, (), (const, override));
|
|
MOCK_METHOD(int, Ssrc, (), (const, override));
|
|
|
|
AudioFrame* fake_frame() { return &fake_frame_; }
|
|
AudioFrameInfo fake_info() { return fake_audio_frame_info_; }
|
|
void set_fake_info(const AudioFrameInfo audio_frame_info) {
|
|
fake_audio_frame_info_ = audio_frame_info;
|
|
}
|
|
|
|
void set_packet_infos(const RtpPacketInfos& packet_infos) {
|
|
packet_infos_ = packet_infos;
|
|
}
|
|
|
|
private:
|
|
AudioFrameInfo FakeAudioFrameWithInfo(int sample_rate_hz,
|
|
AudioFrame* audio_frame) {
|
|
audio_frame->CopyFrom(fake_frame_);
|
|
audio_frame->sample_rate_hz_ = sample_rate_hz;
|
|
audio_frame->samples_per_channel_ =
|
|
rtc::CheckedDivExact(sample_rate_hz, 100);
|
|
audio_frame->packet_infos_ = packet_infos_;
|
|
return fake_info();
|
|
}
|
|
|
|
AudioFrame fake_frame_;
|
|
AudioFrameInfo fake_audio_frame_info_;
|
|
RtpPacketInfos packet_infos_;
|
|
};
|
|
|
|
class CustomRateCalculator : public OutputRateCalculator {
|
|
public:
|
|
explicit CustomRateCalculator(int rate) : rate_(rate) {}
|
|
int CalculateOutputRateFromRange(
|
|
rtc::ArrayView<const int> preferred_rates) override {
|
|
return rate_;
|
|
}
|
|
|
|
private:
|
|
const int rate_;
|
|
};
|
|
|
|
// Creates participants from `frames` and `frame_info` and adds them
|
|
// to the mixer. Compares mixed status with `expected_status`
|
|
void MixAndCompare(
|
|
const std::vector<AudioFrame>& frames,
|
|
const std::vector<AudioMixer::Source::AudioFrameInfo>& frame_info,
|
|
const std::vector<bool>& expected_status) {
|
|
const size_t num_audio_sources = frames.size();
|
|
RTC_DCHECK(frames.size() == frame_info.size());
|
|
RTC_DCHECK(frame_info.size() == expected_status.size());
|
|
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
std::vector<MockMixerAudioSource> participants(num_audio_sources);
|
|
|
|
for (size_t i = 0; i < num_audio_sources; ++i) {
|
|
participants[i].fake_frame()->CopyFrom(frames[i]);
|
|
participants[i].set_fake_info(frame_info[i]);
|
|
}
|
|
|
|
for (size_t i = 0; i < num_audio_sources; ++i) {
|
|
EXPECT_TRUE(mixer->AddSource(&participants[i]));
|
|
EXPECT_CALL(participants[i], GetAudioFrameWithInfo(kDefaultSampleRateHz, _))
|
|
.Times(Exactly(1));
|
|
}
|
|
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
|
|
for (size_t i = 0; i < num_audio_sources; ++i) {
|
|
EXPECT_EQ(expected_status[i],
|
|
mixer->GetAudioSourceMixabilityStatusForTest(&participants[i]))
|
|
<< "Mixed status of AudioSource #" << i << " wrong.";
|
|
}
|
|
}
|
|
|
|
void MixMonoAtGivenNativeRate(int native_sample_rate,
|
|
AudioFrame* mix_frame,
|
|
rtc::scoped_refptr<AudioMixer> mixer,
|
|
MockMixerAudioSource* audio_source) {
|
|
ON_CALL(*audio_source, PreferredSampleRate())
|
|
.WillByDefault(Return(native_sample_rate));
|
|
audio_source->fake_frame()->sample_rate_hz_ = native_sample_rate;
|
|
audio_source->fake_frame()->samples_per_channel_ = native_sample_rate / 100;
|
|
|
|
mixer->Mix(1, mix_frame);
|
|
}
|
|
|
|
TEST(AudioMixer, LargestEnergyVadActiveMixed) {
|
|
constexpr int kAudioSources =
|
|
AudioMixerImpl::kDefaultNumberOfMixedAudioSources + 3;
|
|
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
|
|
MockMixerAudioSource participants[kAudioSources];
|
|
|
|
for (int i = 0; i < kAudioSources; ++i) {
|
|
ResetFrame(participants[i].fake_frame());
|
|
|
|
// We set the 80-th sample value since the first 80 samples may be
|
|
// modified by a ramped-in window.
|
|
participants[i].fake_frame()->mutable_data()[80] = i;
|
|
|
|
EXPECT_TRUE(mixer->AddSource(&participants[i]));
|
|
EXPECT_CALL(participants[i], GetAudioFrameWithInfo(_, _)).Times(Exactly(1));
|
|
}
|
|
|
|
// Last participant gives audio frame with passive VAD, although it has the
|
|
// largest energy.
|
|
participants[kAudioSources - 1].fake_frame()->vad_activity_ =
|
|
AudioFrame::kVadPassive;
|
|
|
|
AudioFrame audio_frame;
|
|
mixer->Mix(1, // number of channels
|
|
&audio_frame);
|
|
|
|
for (int i = 0; i < kAudioSources; ++i) {
|
|
bool is_mixed =
|
|
mixer->GetAudioSourceMixabilityStatusForTest(&participants[i]);
|
|
if (i == kAudioSources - 1 ||
|
|
i < kAudioSources - 1 -
|
|
AudioMixerImpl::kDefaultNumberOfMixedAudioSources) {
|
|
EXPECT_FALSE(is_mixed)
|
|
<< "Mixing status of AudioSource #" << i << " wrong.";
|
|
} else {
|
|
EXPECT_TRUE(is_mixed)
|
|
<< "Mixing status of AudioSource #" << i << " wrong.";
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(AudioMixer, FrameNotModifiedForSingleParticipant) {
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
|
|
MockMixerAudioSource participant;
|
|
|
|
ResetFrame(participant.fake_frame());
|
|
const size_t n_samples = participant.fake_frame()->samples_per_channel_;
|
|
|
|
// Modify the frame so that it's not zero.
|
|
int16_t* fake_frame_data = participant.fake_frame()->mutable_data();
|
|
for (size_t j = 0; j < n_samples; ++j) {
|
|
fake_frame_data[j] = static_cast<int16_t>(j);
|
|
}
|
|
|
|
EXPECT_TRUE(mixer->AddSource(&participant));
|
|
EXPECT_CALL(participant, GetAudioFrameWithInfo(_, _)).Times(Exactly(2));
|
|
|
|
AudioFrame audio_frame;
|
|
// Two mix iteration to compare after the ramp-up step.
|
|
for (int i = 0; i < 2; ++i) {
|
|
mixer->Mix(1, // number of channels
|
|
&audio_frame);
|
|
}
|
|
|
|
EXPECT_EQ(0, memcmp(participant.fake_frame()->data(), audio_frame.data(),
|
|
n_samples));
|
|
}
|
|
|
|
TEST(AudioMixer, SourceAtNativeRateShouldNeverResample) {
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
|
|
MockMixerAudioSource audio_source;
|
|
ResetFrame(audio_source.fake_frame());
|
|
|
|
mixer->AddSource(&audio_source);
|
|
|
|
for (auto frequency : {8000, 16000, 32000, 48000}) {
|
|
EXPECT_CALL(audio_source, GetAudioFrameWithInfo(frequency, _))
|
|
.Times(Exactly(1));
|
|
|
|
MixMonoAtGivenNativeRate(frequency, &frame_for_mixing, mixer,
|
|
&audio_source);
|
|
}
|
|
}
|
|
|
|
TEST(AudioMixer, MixerShouldMixAtNativeSourceRate) {
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
|
|
MockMixerAudioSource audio_source;
|
|
ResetFrame(audio_source.fake_frame());
|
|
|
|
mixer->AddSource(&audio_source);
|
|
|
|
for (auto frequency : {8000, 16000, 32000, 48000}) {
|
|
MixMonoAtGivenNativeRate(frequency, &frame_for_mixing, mixer,
|
|
&audio_source);
|
|
|
|
EXPECT_EQ(frequency, frame_for_mixing.sample_rate_hz_);
|
|
}
|
|
}
|
|
|
|
TEST(AudioMixer, MixerShouldAlwaysMixAtNativeRate) {
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
|
|
MockMixerAudioSource participant;
|
|
ResetFrame(participant.fake_frame());
|
|
mixer->AddSource(&participant);
|
|
|
|
const int needed_frequency = 44100;
|
|
ON_CALL(participant, PreferredSampleRate())
|
|
.WillByDefault(Return(needed_frequency));
|
|
|
|
// We expect mixing frequency to be native and >= needed_frequency.
|
|
const int expected_mix_frequency = 48000;
|
|
EXPECT_CALL(participant, GetAudioFrameWithInfo(expected_mix_frequency, _))
|
|
.Times(Exactly(1));
|
|
participant.fake_frame()->sample_rate_hz_ = expected_mix_frequency;
|
|
participant.fake_frame()->samples_per_channel_ = expected_mix_frequency / 100;
|
|
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
|
|
EXPECT_EQ(48000, frame_for_mixing.sample_rate_hz_);
|
|
}
|
|
|
|
// Check that the mixing rate is always >= participants preferred rate.
|
|
TEST(AudioMixer, ShouldNotCauseQualityLossForMultipleSources) {
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
|
|
std::vector<MockMixerAudioSource> audio_sources(2);
|
|
const std::vector<int> source_sample_rates = {8000, 16000};
|
|
for (int i = 0; i < 2; ++i) {
|
|
auto& source = audio_sources[i];
|
|
ResetFrame(source.fake_frame());
|
|
mixer->AddSource(&source);
|
|
const auto sample_rate = source_sample_rates[i];
|
|
EXPECT_CALL(source, PreferredSampleRate()).WillOnce(Return(sample_rate));
|
|
|
|
EXPECT_CALL(source, GetAudioFrameWithInfo(::testing::Ge(sample_rate), _));
|
|
}
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
}
|
|
|
|
TEST(AudioMixer, ParticipantNumberOfChannels) {
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
|
|
MockMixerAudioSource participant;
|
|
ResetFrame(participant.fake_frame());
|
|
|
|
EXPECT_TRUE(mixer->AddSource(&participant));
|
|
for (size_t number_of_channels : {1, 2}) {
|
|
EXPECT_CALL(participant, GetAudioFrameWithInfo(kDefaultSampleRateHz, _))
|
|
.Times(Exactly(1));
|
|
mixer->Mix(number_of_channels, &frame_for_mixing);
|
|
EXPECT_EQ(number_of_channels, frame_for_mixing.num_channels_);
|
|
}
|
|
}
|
|
|
|
// Maximal amount of participants are mixed one iteration, then
|
|
// another participant with higher energy is added.
|
|
TEST(AudioMixer, RampedOutSourcesShouldNotBeMarkedMixed) {
|
|
constexpr int kAudioSources =
|
|
AudioMixerImpl::kDefaultNumberOfMixedAudioSources + 1;
|
|
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
MockMixerAudioSource participants[kAudioSources];
|
|
|
|
for (int i = 0; i < kAudioSources; ++i) {
|
|
ResetFrame(participants[i].fake_frame());
|
|
// Set the participant audio energy to increase with the index
|
|
// `i`.
|
|
participants[i].fake_frame()->mutable_data()[0] = 100 * i;
|
|
}
|
|
|
|
// Add all participants but the loudest for mixing.
|
|
for (int i = 0; i < kAudioSources - 1; ++i) {
|
|
EXPECT_TRUE(mixer->AddSource(&participants[i]));
|
|
EXPECT_CALL(participants[i], GetAudioFrameWithInfo(kDefaultSampleRateHz, _))
|
|
.Times(Exactly(1));
|
|
}
|
|
|
|
// First mixer iteration
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
|
|
// All participants but the loudest should have been mixed.
|
|
for (int i = 0; i < kAudioSources - 1; ++i) {
|
|
EXPECT_TRUE(mixer->GetAudioSourceMixabilityStatusForTest(&participants[i]))
|
|
<< "Mixed status of AudioSource #" << i << " wrong.";
|
|
}
|
|
|
|
// Add new participant with higher energy.
|
|
EXPECT_TRUE(mixer->AddSource(&participants[kAudioSources - 1]));
|
|
for (int i = 0; i < kAudioSources; ++i) {
|
|
EXPECT_CALL(participants[i], GetAudioFrameWithInfo(kDefaultSampleRateHz, _))
|
|
.Times(Exactly(1));
|
|
}
|
|
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
|
|
// The most quiet participant should not have been mixed.
|
|
EXPECT_FALSE(mixer->GetAudioSourceMixabilityStatusForTest(&participants[0]))
|
|
<< "Mixed status of AudioSource #0 wrong.";
|
|
|
|
// The loudest participants should have been mixed.
|
|
for (int i = 1; i < kAudioSources; ++i) {
|
|
EXPECT_EQ(true,
|
|
mixer->GetAudioSourceMixabilityStatusForTest(&participants[i]))
|
|
<< "Mixed status of AudioSource #" << i << " wrong.";
|
|
}
|
|
}
|
|
|
|
// This test checks that the initialization and participant addition
|
|
// can be done on a different thread.
|
|
TEST(AudioMixer, ConstructFromOtherThread) {
|
|
TaskQueueForTest init_queue("init");
|
|
rtc::scoped_refptr<AudioMixer> mixer;
|
|
init_queue.SendTask([&mixer]() { mixer = AudioMixerImpl::Create(); });
|
|
|
|
MockMixerAudioSource participant;
|
|
EXPECT_CALL(participant, PreferredSampleRate())
|
|
.WillRepeatedly(Return(kDefaultSampleRateHz));
|
|
|
|
ResetFrame(participant.fake_frame());
|
|
|
|
TaskQueueForTest participant_queue("participant");
|
|
participant_queue.SendTask(
|
|
[&mixer, &participant]() { mixer->AddSource(&participant); });
|
|
|
|
EXPECT_CALL(participant, GetAudioFrameWithInfo(kDefaultSampleRateHz, _))
|
|
.Times(Exactly(1));
|
|
|
|
// Do one mixer iteration
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
}
|
|
|
|
TEST(AudioMixer, MutedShouldMixAfterUnmuted) {
|
|
constexpr int kAudioSources =
|
|
AudioMixerImpl::kDefaultNumberOfMixedAudioSources + 1;
|
|
|
|
std::vector<AudioFrame> frames(kAudioSources);
|
|
for (auto& frame : frames) {
|
|
ResetFrame(&frame);
|
|
}
|
|
|
|
std::vector<AudioMixer::Source::AudioFrameInfo> frame_info(
|
|
kAudioSources, AudioMixer::Source::AudioFrameInfo::kNormal);
|
|
frame_info[0] = AudioMixer::Source::AudioFrameInfo::kMuted;
|
|
std::vector<bool> expected_status(kAudioSources, true);
|
|
expected_status[0] = false;
|
|
|
|
MixAndCompare(frames, frame_info, expected_status);
|
|
}
|
|
|
|
TEST(AudioMixer, PassiveShouldMixAfterNormal) {
|
|
constexpr int kAudioSources =
|
|
AudioMixerImpl::kDefaultNumberOfMixedAudioSources + 1;
|
|
|
|
std::vector<AudioFrame> frames(kAudioSources);
|
|
for (auto& frame : frames) {
|
|
ResetFrame(&frame);
|
|
}
|
|
|
|
std::vector<AudioMixer::Source::AudioFrameInfo> frame_info(
|
|
kAudioSources, AudioMixer::Source::AudioFrameInfo::kNormal);
|
|
frames[0].vad_activity_ = AudioFrame::kVadPassive;
|
|
std::vector<bool> expected_status(kAudioSources, true);
|
|
expected_status[0] = false;
|
|
|
|
MixAndCompare(frames, frame_info, expected_status);
|
|
}
|
|
|
|
TEST(AudioMixer, ActiveShouldMixBeforeLoud) {
|
|
constexpr int kAudioSources =
|
|
AudioMixerImpl::kDefaultNumberOfMixedAudioSources + 1;
|
|
|
|
std::vector<AudioFrame> frames(kAudioSources);
|
|
for (auto& frame : frames) {
|
|
ResetFrame(&frame);
|
|
}
|
|
|
|
std::vector<AudioMixer::Source::AudioFrameInfo> frame_info(
|
|
kAudioSources, AudioMixer::Source::AudioFrameInfo::kNormal);
|
|
frames[0].vad_activity_ = AudioFrame::kVadPassive;
|
|
int16_t* frame_data = frames[0].mutable_data();
|
|
std::fill(frame_data, frame_data + kDefaultSampleRateHz / 100,
|
|
std::numeric_limits<int16_t>::max());
|
|
std::vector<bool> expected_status(kAudioSources, true);
|
|
expected_status[0] = false;
|
|
|
|
MixAndCompare(frames, frame_info, expected_status);
|
|
}
|
|
|
|
TEST(AudioMixer, ShouldMixUpToSpecifiedNumberOfSourcesToMix) {
|
|
constexpr int kAudioSources = 5;
|
|
constexpr int kSourcesToMix = 2;
|
|
|
|
std::vector<AudioFrame> frames(kAudioSources);
|
|
for (auto& frame : frames) {
|
|
ResetFrame(&frame);
|
|
}
|
|
|
|
std::vector<AudioMixer::Source::AudioFrameInfo> frame_info(
|
|
kAudioSources, AudioMixer::Source::AudioFrameInfo::kNormal);
|
|
// Set up to kSourceToMix sources with kVadActive so that they're mixed.
|
|
const std::vector<AudioFrame::VADActivity> kVadActivities = {
|
|
AudioFrame::kVadUnknown, AudioFrame::kVadPassive, AudioFrame::kVadPassive,
|
|
AudioFrame::kVadActive, AudioFrame::kVadActive};
|
|
// Populate VAD and frame for all sources.
|
|
for (int i = 0; i < kAudioSources; i++) {
|
|
frames[i].vad_activity_ = kVadActivities[i];
|
|
}
|
|
|
|
std::vector<MockMixerAudioSource> participants(kAudioSources);
|
|
for (int i = 0; i < kAudioSources; ++i) {
|
|
participants[i].fake_frame()->CopyFrom(frames[i]);
|
|
participants[i].set_fake_info(frame_info[i]);
|
|
}
|
|
|
|
const auto mixer = AudioMixerImpl::Create(kSourcesToMix);
|
|
for (int i = 0; i < kAudioSources; ++i) {
|
|
EXPECT_TRUE(mixer->AddSource(&participants[i]));
|
|
EXPECT_CALL(participants[i], GetAudioFrameWithInfo(kDefaultSampleRateHz, _))
|
|
.Times(Exactly(1));
|
|
}
|
|
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
|
|
std::vector<bool> expected_status = {false, false, false, true, true};
|
|
for (int i = 0; i < kAudioSources; ++i) {
|
|
EXPECT_EQ(expected_status[i],
|
|
mixer->GetAudioSourceMixabilityStatusForTest(&participants[i]))
|
|
<< "Wrong mix status for source #" << i << " is wrong";
|
|
}
|
|
}
|
|
|
|
TEST(AudioMixer, UnmutedShouldMixBeforeLoud) {
|
|
constexpr int kAudioSources =
|
|
AudioMixerImpl::kDefaultNumberOfMixedAudioSources + 1;
|
|
|
|
std::vector<AudioFrame> frames(kAudioSources);
|
|
for (auto& frame : frames) {
|
|
ResetFrame(&frame);
|
|
}
|
|
|
|
std::vector<AudioMixer::Source::AudioFrameInfo> frame_info(
|
|
kAudioSources, AudioMixer::Source::AudioFrameInfo::kNormal);
|
|
frame_info[0] = AudioMixer::Source::AudioFrameInfo::kMuted;
|
|
int16_t* frame_data = frames[0].mutable_data();
|
|
std::fill(frame_data, frame_data + kDefaultSampleRateHz / 100,
|
|
std::numeric_limits<int16_t>::max());
|
|
std::vector<bool> expected_status(kAudioSources, true);
|
|
expected_status[0] = false;
|
|
|
|
MixAndCompare(frames, frame_info, expected_status);
|
|
}
|
|
|
|
TEST(AudioMixer, MixingRateShouldBeDecidedByRateCalculator) {
|
|
constexpr int kOutputRate = 22000;
|
|
const auto mixer =
|
|
AudioMixerImpl::Create(std::unique_ptr<OutputRateCalculator>(
|
|
new CustomRateCalculator(kOutputRate)),
|
|
true);
|
|
MockMixerAudioSource audio_source;
|
|
mixer->AddSource(&audio_source);
|
|
ResetFrame(audio_source.fake_frame());
|
|
|
|
EXPECT_CALL(audio_source, GetAudioFrameWithInfo(kOutputRate, _))
|
|
.Times(Exactly(1));
|
|
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
}
|
|
|
|
TEST(AudioMixer, ZeroSourceRateShouldBeDecidedByRateCalculator) {
|
|
constexpr int kOutputRate = 8000;
|
|
const auto mixer =
|
|
AudioMixerImpl::Create(std::unique_ptr<OutputRateCalculator>(
|
|
new CustomRateCalculator(kOutputRate)),
|
|
true);
|
|
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
|
|
EXPECT_EQ(kOutputRate, frame_for_mixing.sample_rate_hz_);
|
|
}
|
|
|
|
TEST(AudioMixer, NoLimiterBasicApiCalls) {
|
|
const auto mixer = AudioMixerImpl::Create(
|
|
std::unique_ptr<OutputRateCalculator>(new DefaultOutputRateCalculator()),
|
|
false);
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
}
|
|
|
|
TEST(AudioMixer, AnyRateIsPossibleWithNoLimiter) {
|
|
// No APM limiter means no AudioProcessing::NativeRate restriction
|
|
// on mixing rate. The rate has to be divisible by 100 since we use
|
|
// 10 ms frames, though.
|
|
for (const auto rate : {8000, 20000, 24000, 32000, 44100}) {
|
|
for (const size_t number_of_channels : {1, 2}) {
|
|
for (const auto number_of_sources : {0, 1, 2, 3, 4}) {
|
|
SCOPED_TRACE(
|
|
ProduceDebugText(rate, number_of_sources, number_of_sources));
|
|
const auto mixer =
|
|
AudioMixerImpl::Create(std::unique_ptr<OutputRateCalculator>(
|
|
new CustomRateCalculator(rate)),
|
|
false);
|
|
|
|
std::vector<MockMixerAudioSource> sources(number_of_sources);
|
|
for (auto& source : sources) {
|
|
ResetFrame(source.fake_frame());
|
|
mixer->AddSource(&source);
|
|
}
|
|
|
|
mixer->Mix(number_of_channels, &frame_for_mixing);
|
|
EXPECT_EQ(rate, frame_for_mixing.sample_rate_hz_);
|
|
EXPECT_EQ(number_of_channels, frame_for_mixing.num_channels_);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(AudioMixer, MultipleChannelsOneParticipant) {
|
|
// Set up a participant with a 6-channel frame, and make sure a 6-channel
|
|
// frame with the right sample values comes out from the mixer. There are 2
|
|
// Mix calls because of ramp-up.
|
|
constexpr size_t kNumberOfChannels = 6;
|
|
MockMixerAudioSource source;
|
|
ResetFrame(source.fake_frame());
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
mixer->AddSource(&source);
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
auto* frame = source.fake_frame();
|
|
frame->num_channels_ = kNumberOfChannels;
|
|
std::fill(frame->mutable_data(),
|
|
frame->mutable_data() + AudioFrame::kMaxDataSizeSamples, 0);
|
|
for (size_t i = 0; i < kNumberOfChannels; ++i) {
|
|
frame->mutable_data()[100 * frame->num_channels_ + i] = 1000 * i;
|
|
}
|
|
|
|
mixer->Mix(kNumberOfChannels, &frame_for_mixing);
|
|
|
|
EXPECT_EQ(frame_for_mixing.num_channels_, kNumberOfChannels);
|
|
for (size_t i = 0; i < kNumberOfChannels; ++i) {
|
|
EXPECT_EQ(frame_for_mixing.data()[100 * frame_for_mixing.num_channels_ + i],
|
|
static_cast<int16_t>(1000 * i));
|
|
}
|
|
}
|
|
|
|
TEST(AudioMixer, MultipleChannelsManyParticipants) {
|
|
// Sets up 2 participants. One has a 6-channel frame. Make sure a 6-channel
|
|
// frame with the right sample values comes out from the mixer. There are 2
|
|
// Mix calls because of ramp-up.
|
|
constexpr size_t kNumberOfChannels = 6;
|
|
MockMixerAudioSource source;
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
mixer->AddSource(&source);
|
|
ResetFrame(source.fake_frame());
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
auto* frame = source.fake_frame();
|
|
frame->num_channels_ = kNumberOfChannels;
|
|
std::fill(frame->mutable_data(),
|
|
frame->mutable_data() + AudioFrame::kMaxDataSizeSamples, 0);
|
|
for (size_t i = 0; i < kNumberOfChannels; ++i) {
|
|
frame->mutable_data()[100 * frame->num_channels_ + i] = 1000 * i;
|
|
}
|
|
MockMixerAudioSource other_source;
|
|
ResetFrame(other_source.fake_frame());
|
|
mixer->AddSource(&other_source);
|
|
|
|
mixer->Mix(kNumberOfChannels, &frame_for_mixing);
|
|
|
|
EXPECT_EQ(frame_for_mixing.num_channels_, kNumberOfChannels);
|
|
for (size_t i = 0; i < kNumberOfChannels; ++i) {
|
|
EXPECT_EQ(frame_for_mixing.data()[100 * frame_for_mixing.num_channels_ + i],
|
|
static_cast<int16_t>(1000 * i));
|
|
}
|
|
}
|
|
|
|
TEST(AudioMixer, ShouldIncludeRtpPacketInfoFromAllMixedSources) {
|
|
const uint32_t kSsrc0 = 10;
|
|
const uint32_t kSsrc1 = 11;
|
|
const uint32_t kSsrc2 = 12;
|
|
const uint32_t kCsrc0 = 20;
|
|
const uint32_t kCsrc1 = 21;
|
|
const uint32_t kCsrc2 = 22;
|
|
const uint32_t kCsrc3 = 23;
|
|
const int kAudioLevel0 = 10;
|
|
const int kAudioLevel1 = 40;
|
|
const absl::optional<uint32_t> kAudioLevel2 = absl::nullopt;
|
|
const uint32_t kRtpTimestamp0 = 300;
|
|
const uint32_t kRtpTimestamp1 = 400;
|
|
const Timestamp kReceiveTime0 = Timestamp::Millis(10);
|
|
const Timestamp kReceiveTime1 = Timestamp::Millis(20);
|
|
|
|
const RtpPacketInfo kPacketInfo0(kSsrc0, {kCsrc0, kCsrc1}, kRtpTimestamp0,
|
|
kAudioLevel0, absl::nullopt, kReceiveTime0);
|
|
const RtpPacketInfo kPacketInfo1(kSsrc1, {kCsrc2}, kRtpTimestamp1,
|
|
kAudioLevel1, absl::nullopt, kReceiveTime1);
|
|
const RtpPacketInfo kPacketInfo2(kSsrc2, {kCsrc3}, kRtpTimestamp1,
|
|
kAudioLevel2, absl::nullopt, kReceiveTime1);
|
|
|
|
const auto mixer = AudioMixerImpl::Create();
|
|
|
|
MockMixerAudioSource source;
|
|
source.set_packet_infos(RtpPacketInfos({kPacketInfo0}));
|
|
mixer->AddSource(&source);
|
|
ResetFrame(source.fake_frame());
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
|
|
MockMixerAudioSource other_source;
|
|
other_source.set_packet_infos(RtpPacketInfos({kPacketInfo1, kPacketInfo2}));
|
|
ResetFrame(other_source.fake_frame());
|
|
mixer->AddSource(&other_source);
|
|
|
|
mixer->Mix(/*number_of_channels=*/1, &frame_for_mixing);
|
|
|
|
EXPECT_THAT(frame_for_mixing.packet_infos_,
|
|
UnorderedElementsAre(kPacketInfo0, kPacketInfo1, kPacketInfo2));
|
|
}
|
|
|
|
TEST(AudioMixer, MixerShouldIncludeRtpPacketInfoFromMixedSourcesOnly) {
|
|
const uint32_t kSsrc0 = 10;
|
|
const uint32_t kSsrc1 = 11;
|
|
const uint32_t kSsrc2 = 21;
|
|
const uint32_t kCsrc0 = 30;
|
|
const uint32_t kCsrc1 = 31;
|
|
const uint32_t kCsrc2 = 32;
|
|
const uint32_t kCsrc3 = 33;
|
|
const int kAudioLevel0 = 10;
|
|
const absl::optional<uint32_t> kAudioLevelMissing = absl::nullopt;
|
|
const uint32_t kRtpTimestamp0 = 300;
|
|
const uint32_t kRtpTimestamp1 = 400;
|
|
const Timestamp kReceiveTime0 = Timestamp::Millis(10);
|
|
const Timestamp kReceiveTime1 = Timestamp::Millis(20);
|
|
|
|
const RtpPacketInfo kPacketInfo0(kSsrc0, {kCsrc0, kCsrc1}, kRtpTimestamp0,
|
|
kAudioLevel0, absl::nullopt, kReceiveTime0);
|
|
const RtpPacketInfo kPacketInfo1(kSsrc1, {kCsrc2}, kRtpTimestamp1,
|
|
kAudioLevelMissing, absl::nullopt,
|
|
kReceiveTime1);
|
|
const RtpPacketInfo kPacketInfo2(kSsrc2, {kCsrc3}, kRtpTimestamp1,
|
|
kAudioLevelMissing, absl::nullopt,
|
|
kReceiveTime1);
|
|
|
|
const auto mixer = AudioMixerImpl::Create(/*max_sources_to_mix=*/2);
|
|
|
|
MockMixerAudioSource source1;
|
|
source1.set_packet_infos(RtpPacketInfos({kPacketInfo0}));
|
|
mixer->AddSource(&source1);
|
|
ResetFrame(source1.fake_frame());
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
|
|
MockMixerAudioSource source2;
|
|
source2.set_packet_infos(RtpPacketInfos({kPacketInfo1}));
|
|
ResetFrame(source2.fake_frame());
|
|
mixer->AddSource(&source2);
|
|
|
|
// The mixer prioritizes kVadActive over kVadPassive.
|
|
// We limit the number of sources to mix to 2 and set the third source's VAD
|
|
// activity to kVadPassive so that it will not be added to the mix.
|
|
MockMixerAudioSource source3;
|
|
source3.set_packet_infos(RtpPacketInfos({kPacketInfo2}));
|
|
ResetFrame(source3.fake_frame());
|
|
source3.fake_frame()->vad_activity_ = AudioFrame::kVadPassive;
|
|
mixer->AddSource(&source3);
|
|
|
|
mixer->Mix(/*number_of_channels=*/1, &frame_for_mixing);
|
|
|
|
EXPECT_THAT(frame_for_mixing.packet_infos_,
|
|
UnorderedElementsAre(kPacketInfo0, kPacketInfo1));
|
|
}
|
|
|
|
class HighOutputRateCalculator : public OutputRateCalculator {
|
|
public:
|
|
static const int kDefaultFrequency = 76000;
|
|
int CalculateOutputRateFromRange(
|
|
rtc::ArrayView<const int> preferred_sample_rates) override {
|
|
return kDefaultFrequency;
|
|
}
|
|
~HighOutputRateCalculator() override {}
|
|
};
|
|
const int HighOutputRateCalculator::kDefaultFrequency;
|
|
|
|
TEST(AudioMixerDeathTest, MultipleChannelsAndHighRate) {
|
|
constexpr size_t kSamplesPerChannel =
|
|
HighOutputRateCalculator::kDefaultFrequency / 100;
|
|
// As many channels as an AudioFrame can fit:
|
|
constexpr size_t kNumberOfChannels =
|
|
AudioFrame::kMaxDataSizeSamples / kSamplesPerChannel;
|
|
MockMixerAudioSource source;
|
|
const auto mixer = AudioMixerImpl::Create(
|
|
std::make_unique<HighOutputRateCalculator>(), true);
|
|
mixer->AddSource(&source);
|
|
ResetFrame(source.fake_frame());
|
|
mixer->Mix(1, &frame_for_mixing);
|
|
auto* frame = source.fake_frame();
|
|
frame->num_channels_ = kNumberOfChannels;
|
|
frame->sample_rate_hz_ = HighOutputRateCalculator::kDefaultFrequency;
|
|
frame->samples_per_channel_ = kSamplesPerChannel;
|
|
|
|
std::fill(frame->mutable_data(),
|
|
frame->mutable_data() + AudioFrame::kMaxDataSizeSamples, 0);
|
|
MockMixerAudioSource other_source;
|
|
ResetFrame(other_source.fake_frame());
|
|
auto* other_frame = other_source.fake_frame();
|
|
other_frame->num_channels_ = kNumberOfChannels;
|
|
other_frame->sample_rate_hz_ = HighOutputRateCalculator::kDefaultFrequency;
|
|
other_frame->samples_per_channel_ = kSamplesPerChannel;
|
|
mixer->AddSource(&other_source);
|
|
|
|
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
|
EXPECT_DEATH(mixer->Mix(kNumberOfChannels, &frame_for_mixing), "");
|
|
#elif !RTC_DCHECK_IS_ON
|
|
mixer->Mix(kNumberOfChannels, &frame_for_mixing);
|
|
EXPECT_EQ(frame_for_mixing.num_channels_, kNumberOfChannels);
|
|
EXPECT_EQ(frame_for_mixing.sample_rate_hz_,
|
|
HighOutputRateCalculator::kDefaultFrequency);
|
|
#endif
|
|
}
|
|
|
|
} // namespace webrtc
|