blob: c5ee9aac6071e23ed2e1d494333a3bae042cbda6 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/media/cma/backend/stream_mixer.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "chromecast/media/cma/backend/audio_output_redirector.h"
#include "chromecast/media/cma/backend/mixer_input.h"
#include "chromecast/media/cma/backend/mock_mixer_source.h"
#include "chromecast/media/cma/backend/mock_post_processor_factory.h"
#include "chromecast/media/cma/backend/mock_redirected_audio_output.h"
#include "chromecast/public/media/mixer_output_stream.h"
#include "chromecast/public/volume_control.h"
#include "media/audio/audio_device_description.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_sample_types.h"
#include "media/base/vector_math.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::NiceMock;
namespace chromecast {
namespace media {
namespace {
using FloatType = ::media::Float32SampleTypeTraits;
// Testing constants that are common to multiple test cases.
const size_t kBytesPerSample = sizeof(int32_t);
const int kNumChannels = 2;
const int kOutputFrames = 256;
// kTestSamplesPerSecond needs to be higher than kLowSampleRateCutoff for the
// mixer to use it.
const int kTestSamplesPerSecond = 48000;
// This array holds |NUM_DATA_SETS| sets of arbitrary interleaved float data.
// Each set holds |NUM_SAMPLES| / kNumChannels frames of data.
#define NUM_DATA_SETS 2u
#define NUM_SAMPLES 64u
// Note: Test data should be represented as 32-bit integers and copied into
// ::media::AudioBus instances, rather than wrapping statically declared float
// arrays. The latter method is brittle, as ::media::AudioBus requires 16-byte
// alignment for internal data.
const int32_t kTestData[NUM_DATA_SETS][NUM_SAMPLES] = {
{
74343736, -1333200799, -1360871126, 1138806283, 1931811865,
1856308487, 649203634, 564640023, 1676630678, 23416591,
-1293255456, 547928305, -976258952, 1840550252, 1714525174,
358704931, 983646295, 1264863573, 442473973, 1222979052,
317404525, 366912613, 1393280948, -1022004648, -2054669405,
-159762261, 1127018745, -1984491787, 1406988336, -693327981,
-1549544744, 1232236854, 970338338, -1750160519, -783213057,
1231504562, 1155296810, -820018779, 1155689800, -1108462340,
-150535168, 1033717023, 2121241397, 1829995370, -1893006836,
-819097508, -495186107, 1001768909, -1441111852, 692174781,
1916569026, -687787473, -910565280, 1695751872, 994166817,
1775451433, 909418522, 492671403, -761744663, -2064315902,
1357716471, -1580019684, 1872702377, -1524457840,
},
{
1951643876, 712069070, 1105286211, 1725522438, -986938388,
229538084, 1042753634, 1888456317, 1477803757, 1486284170,
-340193623, -1828672521, 1418790906, -724453609, -1057163251,
1408558147, -31441309, 1421569750, -1231100836, 545866721,
1430262764, 2107819625, -2077050480, -1128358776, -1799818931,
-1041097926, 1911058583, -1177896929, -1911123008, -929110948,
1267464176, 172218310, -2048128170, -2135590884, 734347065,
1214930283, 1301338583, -326962976, -498269894, -1167887508,
-589067650, 591958162, 592999692, -788367017, -1389422,
1466108561, 386162657, 1389031078, 936083827, -1438801160,
1340850135, -1616803932, -850779335, 1666492408, 1290349909,
-492418001, 659200170, -542374913, -120005682, 1030923147,
-877887021, -870241979, 1322678128, -344799975,
}};
// Compensate for integer arithmetic errors.
const int kMaxDelayErrorUs = 2;
const char kDelayModuleSolib[] = "delay.so";
// Should match # of "processors" blocks below.
const int kNumPostProcessors = 5;
const char kTestPipelineJsonTemplate[] = R"json(
{
"postprocessors": {
"output_streams": [{
"streams": [ "default" ],
"processors": [{
"processor": "%s",
"config": { "delay": %d }
}]
}, {
"streams": [ "assistant-tts" ],
"processors": [{
"processor": "%s",
"config": { "delay": %d }
}]
}, {
"streams": [ "communications" ],
"processors": []
}],
"mix": {
"processors": [{
"processor": "%s",
"config": { "delay": %d }
}]
},
"linearize": {
"processors": [{
"processor": "%s",
"config": { "delay": %d }
}]
}
}
}
)json";
const int kDefaultProcessorDelay = 10;
const int kTtsProcessorDelay = 100;
const int kMixProcessorDelay = 1000;
const int kLinearizeProcessorDelay = 10000;
// Return a scoped pointer filled with the data laid out at |index| above.
std::unique_ptr<::media::AudioBus> GetTestData(size_t index) {
CHECK_LT(index, NUM_DATA_SETS);
int frames = NUM_SAMPLES / kNumChannels;
auto data = ::media::AudioBus::Create(kNumChannels, frames);
data->FromInterleaved<::media::SignedInt32SampleTypeTraits>(kTestData[index],
frames);
return data;
}
class MockMixerOutput : public MixerOutputStream {
public:
MockMixerOutput() {
ON_CALL(*this, Start(_, _))
.WillByDefault(testing::Invoke(this, &MockMixerOutput::StartImpl));
ON_CALL(*this, GetSampleRate())
.WillByDefault(
testing::Invoke(this, &MockMixerOutput::GetSampleRateImpl));
ON_CALL(*this, GetRenderingDelay())
.WillByDefault(
testing::Invoke(this, &MockMixerOutput::GetRenderingDelayImpl));
ON_CALL(*this, OptimalWriteFramesCount())
.WillByDefault(
testing::Invoke(this, &MockMixerOutput::OptimalWriteFramesImpl));
ON_CALL(*this, Write(_, _, _))
.WillByDefault(testing::Invoke(this, &MockMixerOutput::WriteImpl));
}
MOCK_METHOD2(Start, bool(int, int));
MOCK_METHOD0(GetSampleRate, int());
MOCK_METHOD0(GetRenderingDelay,
MediaPipelineBackend::AudioDecoder::RenderingDelay());
MOCK_METHOD0(OptimalWriteFramesCount, int());
MOCK_METHOD3(Write, bool(const float*, int, bool*));
MOCK_METHOD0(Stop, void());
int sample_rate() const { return sample_rate_; }
const std::vector<float>& data() const { return data_; }
void ClearData() { data_.clear(); }
private:
bool StartImpl(int requested_sample_rate, int channels) {
sample_rate_ = requested_sample_rate;
return true;
}
int GetSampleRateImpl() { return sample_rate_; }
MediaPipelineBackend::AudioDecoder::RenderingDelay GetRenderingDelayImpl() {
return MediaPipelineBackend::AudioDecoder::RenderingDelay();
}
int OptimalWriteFramesImpl() { return kOutputFrames; }
bool WriteImpl(const float* data,
int data_size,
bool* out_playback_interrupted) {
*out_playback_interrupted = false;
data_.insert(data_.end(), data, data + data_size);
return true;
}
int sample_rate_ = 0;
std::vector<float> data_;
};
#define EXPECT_CALL_ALL_POSTPROCESSORS(factory, call_sig) \
do { \
for (auto& itr : factory->instances) { \
EXPECT_CALL(*itr.second, call_sig); \
} \
} while (0);
void VerifyAndClearPostProcessors(MockPostProcessorFactory* factory) {
for (auto& itr : factory->instances) {
testing::Mock::VerifyAndClearExpectations(itr.second);
}
}
class MockLoopbackAudioObserver : public CastMediaShlib::LoopbackAudioObserver {
public:
MockLoopbackAudioObserver() = default;
~MockLoopbackAudioObserver() override = default;
MOCK_METHOD6(OnLoopbackAudio,
void(int64_t, SampleFormat, int, int, uint8_t*, int));
MOCK_METHOD0(OnLoopbackInterrupted, void());
MOCK_METHOD0(OnRemoved, void());
private:
DISALLOW_COPY_AND_ASSIGN(MockLoopbackAudioObserver);
};
// Given |inputs|, returns mixed audio data according to the mixing method used
// by the mixer.
std::unique_ptr<::media::AudioBus> GetMixedAudioData(
const std::vector<MockMixerSource*>& inputs,
bool apply_volume = true) {
int read_size = 0;
for (auto* input : inputs) {
CHECK(input);
read_size = std::max(input->data().frames(), read_size);
}
// Verify all inputs are the right size.
for (auto* input : inputs) {
CHECK_EQ(kNumChannels, input->data().channels());
CHECK_LE(read_size, input->data().frames());
}
// Currently, the mixing algorithm is simply to sum the scaled, clipped input
// streams. Go sample-by-sample and mix the data.
auto mixed = ::media::AudioBus::Create(kNumChannels, read_size);
for (int c = 0; c < mixed->channels(); ++c) {
for (int f = 0; f < read_size; ++f) {
float* result = mixed->channel(c) + f;
// Sum the sample from each input stream, scaling each stream.
*result = 0.0;
for (auto* input : inputs) {
if (input->data().frames() > f) {
if (apply_volume) {
*result += *(input->data().channel(c) + f) * input->multiplier();
} else {
*result += *(input->data().channel(c) + f);
}
}
}
// Clamp the mixed sample between 1.0 and -1.0.
*result = std::min(1.0f, std::max(-1.0f, *result));
}
}
return mixed;
}
// Like the method above, but accepts a single input. This returns an AudioBus
// with this input after it is scaled and clipped.
std::unique_ptr<::media::AudioBus> GetMixedAudioData(MockMixerSource* input) {
return GetMixedAudioData(std::vector<MockMixerSource*>(1, input));
}
std::unique_ptr<::media::AudioBus> GetMixedAudioData(
const std::vector<std::unique_ptr<MockMixerSource>>& inputs) {
std::vector<MockMixerSource*> ptrs;
for (const auto& i : inputs) {
ptrs.push_back(i.get());
}
return GetMixedAudioData(ptrs);
}
void ToPlanar(const float* interleaved,
int num_frames,
::media::AudioBus* planar) {
ASSERT_GE(planar->frames(), num_frames);
planar->FromInterleaved<FloatType>(interleaved, num_frames);
}
// Asserts that |expected| matches |actual| exactly.
void CompareAudioData(const ::media::AudioBus& expected,
const ::media::AudioBus& actual) {
ASSERT_EQ(expected.channels(), actual.channels());
ASSERT_EQ(expected.frames(), actual.frames());
for (int c = 0; c < expected.channels(); ++c) {
const float* expected_data = expected.channel(c);
const float* actual_data = actual.channel(c);
for (int f = 0; f < expected.frames(); ++f) {
EXPECT_FLOAT_EQ(*expected_data++, *actual_data++) << c << " " << f;
}
}
}
// Check that
// MediaPipelineBackend::AudioDecoder::RenderingDelay.delay_microseconds is
// within kMaxDelayErrorUs of |delay|
MATCHER_P2(MatchDelay, delay, id, "") {
bool result = std::abs(arg.delay_microseconds - delay) < kMaxDelayErrorUs;
if (!result) {
LOG(ERROR) << "Expected delay_microseconds for " << id << " to be " << delay
<< " but got " << arg.delay_microseconds;
}
return result;
}
// Convert a number of frames at kTestSamplesPerSecond to microseconds
int64_t FramesToDelayUs(int64_t frames) {
return frames * base::Time::kMicrosecondsPerSecond / kTestSamplesPerSecond;
}
#if GTEST_HAS_DEATH_TEST
std::string DeathRegex(const std::string& regex) {
// String arguments aren't passed to CHECK() in official builds.
#if defined(OFFICIAL_BUILD) && defined(NDEBUG)
return "";
#else
return regex;
#endif
}
#endif // GTEST_HAS_DEATH_TEST
} // namespace
class StreamMixerTest : public testing::Test {
protected:
StreamMixerTest() : message_loop_(new base::MessageLoop()) {
auto output = std::make_unique<NiceMock<MockMixerOutput>>();
mock_output_ = output.get();
mixer_ = std::make_unique<StreamMixer>(std::move(output), nullptr,
base::ThreadTaskRunnerHandle::Get());
mixer_->SetVolume(AudioContentType::kMedia, 1.0f);
std::string test_pipeline_json = base::StringPrintf(
kTestPipelineJsonTemplate, kDelayModuleSolib, kDefaultProcessorDelay,
kDelayModuleSolib, kTtsProcessorDelay, kDelayModuleSolib,
kMixProcessorDelay, kDelayModuleSolib, kLinearizeProcessorDelay);
auto factory = std::make_unique<MockPostProcessorFactory>();
pp_factory_ = factory.get();
mixer_->SetNumOutputChannelsForTest(2);
mixer_->ResetPostProcessorsForTest(std::move(factory), test_pipeline_json);
CHECK_EQ(pp_factory_->instances.size(),
static_cast<size_t>(kNumPostProcessors));
}
void WaitForMixer() {
base::RunLoop run_loop1;
message_loop_->task_runner()->PostTask(FROM_HERE, run_loop1.QuitClosure());
run_loop1.Run();
}
void PlaybackOnce() {
// Run one playback iteration.
EXPECT_CALL(*mock_output_, Write(_,
mock_output_->OptimalWriteFramesCount() *
mixer_->num_output_channels(),
_))
.Times(1);
base::RunLoop run_loop;
message_loop_->task_runner()->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
testing::Mock::VerifyAndClearExpectations(mock_output_);
}
MockRedirectedAudioOutput* AddOutputRedirector(
const AudioOutputRedirectionConfig& config,
AudioOutputRedirector** redirector_ptr = nullptr) {
auto redirected_output =
std::make_unique<MockRedirectedAudioOutput>(kNumChannels);
MockRedirectedAudioOutput* redirected_output_ptr = redirected_output.get();
auto redirector = std::make_unique<AudioOutputRedirector>(
config, std::move(redirected_output));
if (redirector_ptr) {
*redirector_ptr = redirector.get();
}
EXPECT_CALL(*redirected_output_ptr, Start(kTestSamplesPerSecond)).Times(1);
mixer_->AddAudioOutputRedirector(std::move(redirector));
return redirected_output_ptr;
}
void CheckRedirectorOutput(
MockRedirectedAudioOutput* redirected_output,
const std::vector<MockMixerSource*>& normal_inputs,
const std::vector<MockMixerSource*>& redirected_inputs,
int num_frames,
bool apply_volume = false) {
auto actual_mixer_output =
::media::AudioBus::Create(kNumChannels, num_frames);
ASSERT_GE(mock_output_->data().size(), static_cast<size_t>(num_frames));
ToPlanar(mock_output_->data().data(), num_frames,
actual_mixer_output.get());
std::unique_ptr<::media::AudioBus> expected_mixer_output;
if (normal_inputs.empty()) {
expected_mixer_output =
::media::AudioBus::Create(kNumChannels, num_frames);
expected_mixer_output->Zero();
} else {
expected_mixer_output = GetMixedAudioData(normal_inputs);
}
CompareAudioData(*expected_mixer_output, *actual_mixer_output);
auto actual_redirected_output =
::media::AudioBus::Create(kNumChannels, num_frames);
ASSERT_GE(redirected_output->last_buffer()->frames(), num_frames);
redirected_output->last_buffer()->CopyPartialFramesTo(
0, num_frames, 0, actual_redirected_output.get());
std::unique_ptr<::media::AudioBus> expected_redirected_output;
if (redirected_inputs.empty()) {
expected_redirected_output =
::media::AudioBus::Create(kNumChannels, num_frames);
expected_redirected_output->Zero();
} else {
expected_redirected_output =
GetMixedAudioData(redirected_inputs, apply_volume);
}
CompareAudioData(*expected_redirected_output, *actual_redirected_output);
}
protected:
const std::unique_ptr<base::MessageLoop> message_loop_;
MockMixerOutput* mock_output_;
std::unique_ptr<StreamMixer> mixer_;
MockPostProcessorFactory* pp_factory_;
DISALLOW_COPY_AND_ASSIGN(StreamMixerTest);
};
TEST_F(StreamMixerTest, AddSingleInput) {
MockMixerSource input(kTestSamplesPerSecond);
EXPECT_CALL(input, InitializeAudioPlayback(_, _)).Times(1);
EXPECT_CALL(*mock_output_, Start(kTestSamplesPerSecond, _)).Times(1);
EXPECT_CALL(*mock_output_, Stop()).Times(0);
mixer_->AddInput(&input);
WaitForMixer();
mixer_.reset();
}
TEST_F(StreamMixerTest, AddMultipleInputs) {
MockMixerSource input1(kTestSamplesPerSecond);
MockMixerSource input2(kTestSamplesPerSecond * 2);
EXPECT_CALL(input1, InitializeAudioPlayback(_, _)).Times(1);
EXPECT_CALL(input2, InitializeAudioPlayback(_, _)).Times(1);
EXPECT_CALL(*mock_output_, Start(kTestSamplesPerSecond, _)).Times(1);
EXPECT_CALL(*mock_output_, Stop()).Times(0);
mixer_->AddInput(&input1);
mixer_->AddInput(&input2);
WaitForMixer();
mixer_.reset();
}
TEST_F(StreamMixerTest, RemoveInput) {
std::vector<std::unique_ptr<MockMixerSource>> inputs;
const int kNumInputs = 3;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(
std::make_unique<MockMixerSource>(kTestSamplesPerSecond * (i + 1)));
}
EXPECT_CALL(*mock_output_, Start(kTestSamplesPerSecond, _)).Times(1);
EXPECT_CALL(*mock_output_, Stop()).Times(0);
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], InitializeAudioPlayback(_, _)).Times(1);
mixer_->AddInput(inputs[i].get());
}
WaitForMixer();
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], FinalizeAudioPlayback()).Times(1);
mixer_->RemoveInput(inputs[i].get());
}
WaitForMixer();
}
TEST_F(StreamMixerTest, WriteFrames) {
std::vector<std::unique_ptr<MockMixerSource>> inputs;
const int kNumInputs = 3;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(std::make_unique<MockMixerSource>(kTestSamplesPerSecond));
}
EXPECT_CALL(*mock_output_, Start(kTestSamplesPerSecond, _)).Times(1);
EXPECT_CALL(*mock_output_, Stop()).Times(0);
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], InitializeAudioPlayback(_, _)).Times(1);
mixer_->AddInput(inputs[i].get());
}
WaitForMixer();
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], FillAudioPlaybackFrames(_, _, _)).Times(1);
}
PlaybackOnce();
mixer_.reset();
}
TEST_F(StreamMixerTest, OneStreamMixesProperly) {
MockMixerSource input(kTestSamplesPerSecond);
EXPECT_CALL(input, InitializeAudioPlayback(_, _)).Times(1);
EXPECT_CALL(*mock_output_, Start(kTestSamplesPerSecond, _)).Times(1);
EXPECT_CALL(*mock_output_, Stop()).Times(0);
mixer_->AddInput(&input);
WaitForMixer();
mock_output_->ClearData();
// Populate the stream with data.
const int kNumFrames = 32;
input.SetData(GetTestData(0));
EXPECT_CALL(input, FillAudioPlaybackFrames(_, _, _)).Times(1);
PlaybackOnce();
// Get the actual mixed output, and compare it against the expected stream.
// The stream should match exactly.
auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
ASSERT_GE(mock_output_->data().size(), static_cast<size_t>(kNumFrames));
ToPlanar(mock_output_->data().data(), kNumFrames, actual.get());
auto expected = GetMixedAudioData(&input);
CompareAudioData(*expected, *actual);
mixer_.reset();
}
TEST_F(StreamMixerTest, OneStreamIsScaledDownProperly) {
MockMixerSource input(kTestSamplesPerSecond);
EXPECT_CALL(input, InitializeAudioPlayback(_, _)).Times(1);
EXPECT_CALL(*mock_output_, Start(kTestSamplesPerSecond, _)).Times(1);
EXPECT_CALL(*mock_output_, Stop()).Times(0);
input.set_multiplier(0.75f);
mixer_->AddInput(&input);
mixer_->SetVolumeMultiplier(&input, input.multiplier());
WaitForMixer();
// Populate the stream with data.
const int kNumFrames = 32;
ASSERT_EQ(sizeof(kTestData[0]), kNumChannels * kNumFrames * kBytesPerSample);
input.SetData(GetTestData(0));
mock_output_->ClearData();
EXPECT_CALL(input, FillAudioPlaybackFrames(_, _, _)).Times(1);
PlaybackOnce();
// Get the actual mixed output, and compare it against the expected stream.
// The stream should match exactly.
auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
ASSERT_GE(mock_output_->data().size(), static_cast<size_t>(kNumFrames));
ToPlanar(mock_output_->data().data(), kNumFrames, actual.get());
auto expected = GetMixedAudioData(&input);
CompareAudioData(*expected, *actual);
mixer_.reset();
}
TEST_F(StreamMixerTest, TwoUnscaledStreamsMixProperly) {
// Create a group of input streams.
std::vector<std::unique_ptr<MockMixerSource>> inputs;
const int kNumInputs = 2;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(std::make_unique<MockMixerSource>(kTestSamplesPerSecond));
}
EXPECT_CALL(*mock_output_, Start(kTestSamplesPerSecond, _)).Times(1);
EXPECT_CALL(*mock_output_, Stop()).Times(0);
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], InitializeAudioPlayback(_, _)).Times(1);
mixer_->AddInput(inputs[i].get());
}
WaitForMixer();
mock_output_->ClearData();
// Populate the streams with data.
const int kNumFrames = 32;
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
EXPECT_CALL(*inputs[i], FillAudioPlaybackFrames(_, _, _)).Times(1);
}
PlaybackOnce();
// Get the actual mixed output, and compare it against the expected stream.
// The stream should match exactly.
auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
ASSERT_GE(mock_output_->data().size(), static_cast<size_t>(kNumFrames));
ToPlanar(mock_output_->data().data(), kNumFrames, actual.get());
auto expected = GetMixedAudioData(inputs);
CompareAudioData(*expected, *actual);
mixer_.reset();
}
TEST_F(StreamMixerTest, TwoUnscaledStreamsWithDifferentIdsMixProperly) {
// Create a group of input streams.
std::vector<std::unique_ptr<MockMixerSource>> inputs;
inputs.push_back(std::make_unique<MockMixerSource>(
kTestSamplesPerSecond,
::media::AudioDeviceDescription::kDefaultDeviceId));
inputs.push_back(std::make_unique<MockMixerSource>(
kTestSamplesPerSecond,
::media::AudioDeviceDescription::kCommunicationsDeviceId));
EXPECT_CALL(*mock_output_, Start(kTestSamplesPerSecond, _)).Times(1);
EXPECT_CALL(*mock_output_, Stop()).Times(0);
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], InitializeAudioPlayback(_, _)).Times(1);
mixer_->AddInput(inputs[i].get());
}
WaitForMixer();
mock_output_->ClearData();
// Populate the streams with data.
const int kNumFrames = 32;
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
EXPECT_CALL(*inputs[i], FillAudioPlaybackFrames(_, _, _)).Times(1);
}
PlaybackOnce();
// Get the actual mixed output, and compare it against the expected stream.
// The stream should match exactly.
auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
ASSERT_GE(mock_output_->data().size(), static_cast<size_t>(kNumFrames));
ToPlanar(mock_output_->data().data(), kNumFrames, actual.get());
auto expected = GetMixedAudioData(inputs);
CompareAudioData(*expected, *actual);
mixer_.reset();
}
TEST_F(StreamMixerTest, TwoUnscaledStreamsMixProperlyWithEdgeCases) {
// Create a group of input streams.
std::vector<std::unique_ptr<MockMixerSource>> inputs;
const int kNumInputs = 2;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(std::make_unique<MockMixerSource>(kTestSamplesPerSecond));
}
EXPECT_CALL(*mock_output_, Start(kTestSamplesPerSecond, _)).Times(1);
EXPECT_CALL(*mock_output_, Stop()).Times(0);
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], InitializeAudioPlayback(_, _)).Times(1);
mixer_->AddInput(inputs[i].get());
}
WaitForMixer();
mock_output_->ClearData();
// Create edge case data for the inputs. By mixing these two short streams,
// every combination of {-(2^31), 0, 2^31-1} is tested. This test case is
// intended to be a hand-checkable gut check.
// Note: Test data should be represented as 32-bit integers and copied into
// ::media::AudioBus instances, rather than wrapping statically declared float
// arrays. The latter method is brittle, as ::media::AudioBus requires 16-bit
// alignment for internal data.
const int kNumFrames = 4;
const int32_t kMaxSample = std::numeric_limits<int32_t>::max();
const int32_t kMinSample = std::numeric_limits<int32_t>::min();
const int32_t kEdgeData[2][8] = {
{
kMinSample, kMinSample, kMinSample, 0.0, 0.0, kMaxSample, 0.0, 0.0,
},
{
kMinSample, 0.0, kMaxSample, 0.0, kMaxSample, kMaxSample, 0.0, 0.0,
}};
// Hand-calculate the results. Index 0 is clamped to -(2^31). Index 5 is
// clamped to 2^31-1.
const int32_t kResult[8] = {
kMinSample, kMinSample, 0.0, 0.0, kMaxSample, kMaxSample, 0.0, 0.0,
};
// Populate the streams with data.
for (size_t i = 0; i < inputs.size(); ++i) {
auto test_data = ::media::AudioBus::Create(kNumChannels, kNumFrames);
test_data->FromInterleaved<::media::SignedInt32SampleTypeTraits>(
kEdgeData[i], kNumFrames);
inputs[i]->SetData(std::move(test_data));
EXPECT_CALL(*inputs[i], FillAudioPlaybackFrames(_, _, _)).Times(1);
}
PlaybackOnce();
// Get the actual mixed output, and compare it against the expected stream.
// The stream should match exactly.
auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
ASSERT_GE(mock_output_->data().size(), static_cast<size_t>(kNumFrames));
ToPlanar(mock_output_->data().data(), kNumFrames, actual.get());
// Use the hand-calculated results above.
auto expected = ::media::AudioBus::Create(kNumChannels, kNumFrames);
expected->FromInterleaved<::media::SignedInt32SampleTypeTraits>(kResult,
kNumFrames);
CompareAudioData(*expected, *actual);
mixer_.reset();
}
#define EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(map, name, times, frames, \
silence) \
do { \
auto itr = map->find(name); \
CHECK(itr != map->end()) << "Could not find processor for " << name; \
EXPECT_CALL(*(itr->second), ProcessFrames(_, frames, _, silence)) \
.Times(times); \
} while (0);
TEST_F(StreamMixerTest, PostProcessorDelayListedDeviceId) {
int common_delay = kMixProcessorDelay + kLinearizeProcessorDelay;
std::vector<std::unique_ptr<MockMixerSource>> inputs;
std::vector<int64_t> delays;
inputs.push_back(
std::make_unique<MockMixerSource>(kTestSamplesPerSecond, "default"));
delays.push_back(common_delay + kDefaultProcessorDelay);
inputs.push_back(std::make_unique<MockMixerSource>(kTestSamplesPerSecond,
"communications"));
delays.push_back(common_delay);
inputs.push_back(std::make_unique<MockMixerSource>(kTestSamplesPerSecond,
"assistant-tts"));
delays.push_back(common_delay + kTtsProcessorDelay);
// Convert delay from frames to microseconds.
std::transform(delays.begin(), delays.end(), delays.begin(),
&FramesToDelayUs);
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], InitializeAudioPlayback(_, _)).Times(1);
mixer_->AddInput(inputs[i].get());
}
WaitForMixer();
auto* post_processors = &pp_factory_->instances;
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "default", 1, _,
false);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "mix", 1, _, false);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "linearize", 1, _,
false);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "communications", 1,
_, false);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "assistant-tts", 1,
_, false);
// Poll the inputs for data. Each input will get a different
// rendering delay based on their device type.
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(0));
EXPECT_CALL(*inputs[i],
FillAudioPlaybackFrames(
_, MatchDelay(delays[i], inputs[i]->device_id()), _))
.Times(1);
}
PlaybackOnce();
mixer_.reset();
}
TEST_F(StreamMixerTest, PostProcessorDelayUnlistedDevice) {
const std::string device_id = "not-a-device-id";
MockMixerSource input(kTestSamplesPerSecond, device_id);
input.SetData(GetTestData(0));
auto* post_processors = &pp_factory_->instances;
// These will be called once to ensure their buffers are initialized.
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "default",
testing::AtLeast(1), _, _);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "mix",
testing::AtLeast(1), _, _);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "linearize",
testing::AtLeast(1), _, _);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "communications",
testing::AtLeast(1), _, _);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "assistant-tts",
testing::AtLeast(1), _, _);
mixer_->AddInput(&input);
WaitForMixer();
VerifyAndClearPostProcessors(pp_factory_);
input.SetData(GetTestData(0));
// Delay should be based on default processor.
int64_t delay = FramesToDelayUs(
kDefaultProcessorDelay + kLinearizeProcessorDelay + kMixProcessorDelay);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "default", 1, _,
false);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "mix", 1, _, false);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "linearize", 1, _,
false);
EXPECT_CALL(input,
FillAudioPlaybackFrames(_, MatchDelay(delay, device_id), _))
.Times(1);
PlaybackOnce();
mixer_.reset();
}
TEST_F(StreamMixerTest, PostProcessorRingingWithoutInput) {
const char kTestPipelineJson[] = R"json(
{
"postprocessors": {
"output_streams": [{
"streams": [ "default" ],
"processors": [{
"processor": "%s",
"config": { "delay": 0, "ringing": true}
}]
}, {
"streams": [ "assistant-tts" ],
"processors": [{
"processor": "%s",
"config": { "delay": 0, "ringing": true}
}]
}]
}
}
)json";
MockMixerSource input(kTestSamplesPerSecond, "default");
input.SetData(GetTestData(0));
std::string test_pipeline_json = base::StringPrintf(
kTestPipelineJson, kDelayModuleSolib, kDelayModuleSolib);
auto factory = std::make_unique<MockPostProcessorFactory>();
MockPostProcessorFactory* factory_ptr = factory.get();
mixer_->ResetPostProcessorsForTest(std::move(factory), test_pipeline_json);
mixer_->AddInput(&input);
WaitForMixer();
// "mix" + "linearize" should be automatic
CHECK_EQ(factory_ptr->instances.size(), 4u);
auto* post_processors = &factory_ptr->instances;
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "default", 1, _, _);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "mix", 1, _, false);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "linearize", 1, _,
false);
EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "assistant-tts", 1,
_, true);
PlaybackOnce();
mixer_.reset();
}
TEST_F(StreamMixerTest, PostProcessorProvidesDefaultPipeline) {
auto factory = std::make_unique<MockPostProcessorFactory>();
MockPostProcessorFactory* factory_ptr = factory.get();
mixer_->ResetPostProcessorsForTest(std::move(factory), "{}");
auto* instances = &factory_ptr->instances;
CHECK(instances->find("default") == instances->end());
CHECK(instances->find("mix") != instances->end());
CHECK(instances->find("linearize") != instances->end());
CHECK_EQ(instances->size(), 2u);
}
TEST_F(StreamMixerTest, MultiplePostProcessorsInOneStream) {
const char kJsonTemplate[] = R"json(
{
"postprocessors": {
"output_streams": [{
"streams": [ "default" ],
"processors": [{
"processor": "%s",
"name": "%s",
"config": { "delay": 10 }
}, {
"processor": "%s",
"name": "%s",
"config": { "delay": 100 }
}]
}],
"mix": {
"processors": [{
"processor": "%s",
"name": "%s",
"config": { "delay": 1000 }
}, {
"processor": "%s",
"config": { "delay": 10000 }
}]
}
}
}
)json";
std::string json = base::StringPrintf(
kJsonTemplate, kDelayModuleSolib, "delayer_1", // unique processor name
kDelayModuleSolib, "delayer_2", // non-unique processor names
kDelayModuleSolib, "delayer_2",
kDelayModuleSolib // intentionally omitted processor name
);
auto factory = std::make_unique<MockPostProcessorFactory>();
MockPostProcessorFactory* factory_ptr = factory.get();
mixer_->ResetPostProcessorsForTest(std::move(factory), json);
// "mix" + "linearize" + "default"
EXPECT_EQ(factory_ptr->instances.size(), 3u);
auto* post_processors = &factory_ptr->instances;
EXPECT_EQ(post_processors->find("default")->second->delay(), 110);
EXPECT_EQ(post_processors->find("mix")->second->delay(), 11000);
EXPECT_EQ(post_processors->find("linearize")->second->delay(), 0);
}
TEST_F(StreamMixerTest, PicksPlayoutChannel) {
auto factory = std::make_unique<MockPostProcessorFactory>();
MockPostProcessorFactory* factory_ptr = factory.get();
mixer_->ResetPostProcessorsForTest(std::move(factory), "{}");
MockMixerSource input1(kTestSamplesPerSecond);
MockMixerSource input2(kTestSamplesPerSecond);
MockMixerSource input3(kTestSamplesPerSecond);
MockMixerSource input4(kTestSamplesPerSecond);
input1.set_playout_channel(kChannelAll);
input2.set_playout_channel(0);
input3.set_playout_channel(1);
input4.set_playout_channel(1);
// Requests: all = 0 ch0 = 0 ch1 = 1.
EXPECT_CALL_ALL_POSTPROCESSORS(factory_ptr, UpdatePlayoutChannel(1));
mixer_->AddInput(&input3);
WaitForMixer();
VerifyAndClearPostProcessors(factory_ptr);
// Requests: all = 0 ch0 = 0 ch1 = 2.
// Playout channel is still 1.
mixer_->AddInput(&input4);
WaitForMixer();
VerifyAndClearPostProcessors(factory_ptr);
// Requests: all = 1 ch0 = 0 ch1 = 2.
// Prioritizes all.
EXPECT_CALL_ALL_POSTPROCESSORS(factory_ptr,
UpdatePlayoutChannel(kChannelAll));
mixer_->AddInput(&input1);
WaitForMixer();
VerifyAndClearPostProcessors(factory_ptr);
// Requests: all = 1 ch0 = 0 ch1 = 1.
// Playout channel is still 'all'.
mixer_->RemoveInput(&input3);
WaitForMixer();
VerifyAndClearPostProcessors(factory_ptr);
// Requests: all = 0 ch0 = 0 ch1 = 1.
EXPECT_CALL_ALL_POSTPROCESSORS(factory_ptr, UpdatePlayoutChannel(1));
mixer_->RemoveInput(&input1);
WaitForMixer();
VerifyAndClearPostProcessors(factory_ptr);
// Requests: all = 0 ch0 = 0 ch1 = 0.
EXPECT_CALL_ALL_POSTPROCESSORS(factory_ptr,
UpdatePlayoutChannel(kChannelAll));
mixer_->RemoveInput(&input4);
WaitForMixer();
VerifyAndClearPostProcessors(factory_ptr);
// Requests: all = 0 ch0 = 1 ch1 = 0
EXPECT_CALL_ALL_POSTPROCESSORS(factory_ptr, UpdatePlayoutChannel(0));
mixer_->AddInput(&input2);
WaitForMixer();
VerifyAndClearPostProcessors(factory_ptr);
// Requests: all = 1 ch0 = 1 ch1 = 0
EXPECT_CALL_ALL_POSTPROCESSORS(factory_ptr,
UpdatePlayoutChannel(kChannelAll));
mixer_->AddInput(&input1);
WaitForMixer();
VerifyAndClearPostProcessors(factory_ptr);
mixer_->RemoveInput(&input1);
mixer_->RemoveInput(&input2);
WaitForMixer();
}
TEST_F(StreamMixerTest, SetPostProcessorConfig) {
std::string name = "ThisIsMyName";
std::string config = "ThisIsMyConfig";
for (auto const& x : pp_factory_->instances) {
EXPECT_CALL(*(x.second), SetPostProcessorConfig(name, config));
}
mixer_->SetPostProcessorConfig(name, config);
WaitForMixer();
}
TEST_F(StreamMixerTest, ObserverGets2ChannelsByDefault) {
MockMixerSource input(kTestSamplesPerSecond);
testing::StrictMock<MockLoopbackAudioObserver> observer;
mixer_->AddInput(&input);
mixer_->AddLoopbackAudioObserver(&observer);
EXPECT_CALL(observer,
OnLoopbackAudio(_, kSampleFormatF32, kTestSamplesPerSecond,
kNumChannels, _, _))
.Times(testing::AtLeast(1));
WaitForMixer();
PlaybackOnce();
EXPECT_CALL(observer, OnRemoved());
mixer_->RemoveLoopbackAudioObserver(&observer);
WaitForMixer();
mixer_.reset();
}
TEST_F(StreamMixerTest, ObserverGets1ChannelIfNumOutputChannelsIs1) {
const int kNumOutputChannels = 1;
mixer_->SetNumOutputChannelsForTest(kNumOutputChannels);
MockMixerSource input(kTestSamplesPerSecond);
testing::StrictMock<MockLoopbackAudioObserver> observer;
mixer_->AddInput(&input);
mixer_->AddLoopbackAudioObserver(&observer);
EXPECT_CALL(observer,
OnLoopbackAudio(_, kSampleFormatF32, kTestSamplesPerSecond,
kNumOutputChannels, _, _))
.Times(testing::AtLeast(1));
WaitForMixer();
PlaybackOnce();
EXPECT_CALL(observer, OnRemoved());
mixer_->RemoveLoopbackAudioObserver(&observer);
WaitForMixer();
mixer_.reset();
}
TEST_F(StreamMixerTest, OneStreamOutputRedirection) {
AudioOutputRedirectionConfig config;
config.stream_match_patterns.push_back({AudioContentType::kMedia, "*"});
MockRedirectedAudioOutput* redirected_output_ptr =
AddOutputRedirector(config);
WaitForMixer();
MockMixerSource input(kTestSamplesPerSecond);
mixer_->AddInput(&input);
WaitForMixer();
mock_output_->ClearData();
// Populate the stream with data.
const int kNumFrames = 32;
input.SetData(GetTestData(0));
EXPECT_CALL(input, FillAudioPlaybackFrames(_, _, _)).Times(2);
EXPECT_CALL(*redirected_output_ptr, WriteBuffer(1, _, kOutputFrames, _))
.Times(2);
PlaybackOnce(); // First buffer is faded in, so don't try to compare it.
input.SetData(GetTestData(0));
PlaybackOnce();
CheckRedirectorOutput(redirected_output_ptr, {}, {&input}, kNumFrames);
mixer_.reset();
}
TEST_F(StreamMixerTest, OutputRedirectionOrder) {
AudioOutputRedirectionConfig config;
config.stream_match_patterns.push_back({AudioContentType::kMedia, "*"});
MockRedirectedAudioOutput* redirected_output_ptr1 =
AddOutputRedirector(config);
config.order = 1;
MockRedirectedAudioOutput* redirected_output_ptr2 =
AddOutputRedirector(config);
WaitForMixer();
MockMixerSource input(kTestSamplesPerSecond);
mixer_->AddInput(&input);
WaitForMixer();
mock_output_->ClearData();
// Populate the stream with data.
const int kNumFrames = 32;
input.SetData(GetTestData(0));
EXPECT_CALL(input, FillAudioPlaybackFrames(_, _, _)).Times(2);
EXPECT_CALL(*redirected_output_ptr1, WriteBuffer(1, _, kOutputFrames, _))
.Times(2);
EXPECT_CALL(*redirected_output_ptr2, WriteBuffer(0, _, kOutputFrames, _))
.Times(2);
PlaybackOnce(); // First buffer is faded in, so don't try to compare it.
input.SetData(GetTestData(0));
PlaybackOnce();
// First redirector should produce actual data, since it has a lower order.
CheckRedirectorOutput(redirected_output_ptr1, {}, {&input}, kNumFrames);
// Second redirector should produce silence.
CheckRedirectorOutput(redirected_output_ptr2, {}, {}, kNumFrames);
mixer_.reset();
}
TEST_F(StreamMixerTest, TwoStreamsOutputRedirection) {
std::vector<std::unique_ptr<MockMixerSource>> inputs;
const int kNumInputs = 2;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(std::make_unique<MockMixerSource>(kTestSamplesPerSecond));
}
for (size_t i = 0; i < inputs.size(); ++i) {
mixer_->AddInput(inputs[i].get());
}
WaitForMixer();
mock_output_->ClearData();
AudioOutputRedirectionConfig config;
config.stream_match_patterns.push_back({AudioContentType::kMedia, "*"});
MockRedirectedAudioOutput* redirected_output_ptr =
AddOutputRedirector(config);
WaitForMixer();
EXPECT_CALL(*redirected_output_ptr, WriteBuffer(2, _, kOutputFrames, _))
.Times(2);
const int kNumFrames = 32;
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
CheckRedirectorOutput(redirected_output_ptr, {},
{inputs[0].get(), inputs[1].get()}, kNumFrames);
mixer_.reset();
}
TEST_F(StreamMixerTest, TwoStreamsOutputRedirectionWithVolume) {
std::vector<std::unique_ptr<MockMixerSource>> inputs;
const int kNumInputs = 2;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(std::make_unique<MockMixerSource>(kTestSamplesPerSecond));
}
inputs[0]->set_multiplier(0.5);
inputs[1]->set_multiplier(0.7);
for (size_t i = 0; i < inputs.size(); ++i) {
mixer_->AddInput(inputs[i].get());
mixer_->SetVolumeMultiplier(inputs[i].get(), inputs[i]->multiplier());
}
WaitForMixer();
mock_output_->ClearData();
AudioOutputRedirectionConfig config;
config.stream_match_patterns.push_back({AudioContentType::kMedia, "*"});
config.apply_volume = true;
MockRedirectedAudioOutput* redirected_output_ptr =
AddOutputRedirector(config);
WaitForMixer();
EXPECT_CALL(*redirected_output_ptr, WriteBuffer(2, _, kOutputFrames, _))
.Times(2);
const int kNumFrames = 32;
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
CheckRedirectorOutput(redirected_output_ptr, {},
{inputs[0].get(), inputs[1].get()}, kNumFrames,
true /* apply_volume */);
mixer_.reset();
}
TEST_F(StreamMixerTest, OutputRedirectionMatchDeviceId) {
std::vector<std::unique_ptr<MockMixerSource>> inputs;
inputs.push_back(
std::make_unique<MockMixerSource>(kTestSamplesPerSecond, "matches"));
inputs.push_back(
std::make_unique<MockMixerSource>(kTestSamplesPerSecond, "asdf"));
for (size_t i = 0; i < inputs.size(); ++i) {
mixer_->AddInput(inputs[i].get());
}
WaitForMixer();
mock_output_->ClearData();
AudioOutputRedirectionConfig config;
config.stream_match_patterns.push_back({AudioContentType::kMedia, "*match*"});
MockRedirectedAudioOutput* redirected_output_ptr =
AddOutputRedirector(config);
WaitForMixer();
EXPECT_CALL(*redirected_output_ptr, WriteBuffer(1, _, kOutputFrames, _))
.Times(2);
const int kNumFrames = 32;
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
mock_output_->ClearData();
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
CheckRedirectorOutput(redirected_output_ptr, {inputs[1].get()},
{inputs[0].get()}, kNumFrames);
mixer_.reset();
}
TEST_F(StreamMixerTest, OutputRedirectionMatchContentType) {
std::vector<std::unique_ptr<MockMixerSource>> inputs;
inputs.push_back(
std::make_unique<MockMixerSource>(kTestSamplesPerSecond, "matches"));
inputs.push_back(
std::make_unique<MockMixerSource>(kTestSamplesPerSecond, "matches"));
inputs[0]->set_content_type(AudioContentType::kAlarm);
for (size_t i = 0; i < inputs.size(); ++i) {
mixer_->AddInput(inputs[i].get());
}
WaitForMixer();
mock_output_->ClearData();
AudioOutputRedirectionConfig config;
config.stream_match_patterns.push_back({AudioContentType::kAlarm, "*match*"});
MockRedirectedAudioOutput* redirected_output_ptr =
AddOutputRedirector(config);
WaitForMixer();
EXPECT_CALL(*redirected_output_ptr, WriteBuffer(1, _, kOutputFrames, _))
.Times(2);
const int kNumFrames = 32;
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
mock_output_->ClearData();
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
CheckRedirectorOutput(redirected_output_ptr, {inputs[1].get()},
{inputs[0].get()}, kNumFrames);
mixer_.reset();
}
TEST_F(StreamMixerTest, OutputRedirectionNoMatch) {
std::vector<std::unique_ptr<MockMixerSource>> inputs;
inputs.push_back(std::make_unique<MockMixerSource>(kTestSamplesPerSecond));
inputs.push_back(std::make_unique<MockMixerSource>(kTestSamplesPerSecond));
for (size_t i = 0; i < inputs.size(); ++i) {
mixer_->AddInput(inputs[i].get());
}
WaitForMixer();
mock_output_->ClearData();
AudioOutputRedirectionConfig config;
config.stream_match_patterns.push_back({AudioContentType::kAlarm, "*"});
MockRedirectedAudioOutput* redirected_output_ptr =
AddOutputRedirector(config);
WaitForMixer();
EXPECT_CALL(*redirected_output_ptr, WriteBuffer(0, _, kOutputFrames, _))
.Times(2);
const int kNumFrames = 32;
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
mock_output_->ClearData();
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
CheckRedirectorOutput(redirected_output_ptr,
{inputs[0].get(), inputs[1].get()}, {}, kNumFrames);
mixer_.reset();
}
TEST_F(StreamMixerTest, ModifyOutputRedirection) {
std::vector<std::unique_ptr<MockMixerSource>> inputs;
inputs.push_back(
std::make_unique<MockMixerSource>(kTestSamplesPerSecond, "matches"));
inputs.push_back(
std::make_unique<MockMixerSource>(kTestSamplesPerSecond, "asdf"));
for (size_t i = 0; i < inputs.size(); ++i) {
mixer_->AddInput(inputs[i].get());
}
WaitForMixer();
mock_output_->ClearData();
AudioOutputRedirectionConfig config;
config.stream_match_patterns.push_back({AudioContentType::kMedia, "*match*"});
AudioOutputRedirector* redirector_ptr = nullptr;
MockRedirectedAudioOutput* redirected_output_ptr =
AddOutputRedirector(config, &redirector_ptr);
CHECK(redirector_ptr);
WaitForMixer();
const int kNumFrames = 32;
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
mock_output_->ClearData();
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
CheckRedirectorOutput(redirected_output_ptr, {inputs[1].get()},
{inputs[0].get()}, kNumFrames);
std::vector<std::pair<AudioContentType, std::string>> new_match_patterns = {
{AudioContentType::kMedia, "*asdf*"}};
mixer_->ModifyAudioOutputRedirection(redirector_ptr,
std::move(new_match_patterns));
WaitForMixer();
mock_output_->ClearData();
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
mock_output_->ClearData();
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
}
PlaybackOnce();
CheckRedirectorOutput(redirected_output_ptr, {inputs[0].get()},
{inputs[1].get()}, kNumFrames);
mixer_.reset();
}
#if GTEST_HAS_DEATH_TEST
using StreamMixerDeathTest = StreamMixerTest;
TEST_F(StreamMixerDeathTest, InvalidStreamTypeCrashes) {
const char json[] = R"json(
{
"postprocessors": {
"output_streams": [{
"streams": [ "foobar" ],
"processors": [{
"processor": "dont_care.so",
"config": { "delay": 0 }
}]
}]
}
}
)json";
::testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_DEATH(mixer_->ResetPostProcessorsForTest(
std::make_unique<MockPostProcessorFactory>(), json),
DeathRegex("foobar is not a stream type"));
}
TEST_F(StreamMixerDeathTest, BadJsonCrashes) {
const std::string json("{{");
::testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_DEATH(mixer_->ResetPostProcessorsForTest(
std::make_unique<MockPostProcessorFactory>(), json),
DeathRegex("Invalid JSON"));
}
TEST_F(StreamMixerDeathTest, CrashesIfChannelCountDoesNotMatchFlags) {
const int kNumOutputChannels = 2;
const std::string config = R"Json({
"postprocessors": {
"linearize": {
"processors": [{
"processor": "delay.so",
"config": { "output_channels": 4,
"delay": 0 }
}]
}
}})Json";
::testing::FLAGS_gtest_death_test_style = "threadsafe";
mixer_->SetNumOutputChannelsForTest(kNumOutputChannels);
ASSERT_DEATH(mixer_->ResetPostProcessorsForTest(
std::make_unique<MockPostProcessorFactory>(), config),
DeathRegex("PostProcessorsHaveCorrectNumOutputs"));
}
TEST_F(StreamMixerDeathTest, CrashesIfMoreThan2LoopbackChannels) {
const int kNumOutputChannels = 2;
const std::string config = R"Json({
"postprocessors": {
"output_streams": [{
"streams": [ "default" ],
"processors": [{
"processor": "delay.so",
"config": { "output_channels": 4,
"delay": 0 }
}]
}],
"linearize": {
"processors": [{
"processor": "delay.so",
"config": { "output_channels": 2,
"delay": 0 }
}]
}
}})Json";
::testing::FLAGS_gtest_death_test_style = "threadsafe";
mixer_->SetNumOutputChannelsForTest(kNumOutputChannels);
ASSERT_DEATH(mixer_->ResetPostProcessorsForTest(
std::make_unique<MockPostProcessorFactory>(), config),
DeathRegex("PostProcessorsHaveCorrectNumOutputs"));
}
#endif // GTEST_HAS_DEATH_TEST
} // namespace media
} // namespace chromecast