blob: db5e3421bb6a6df36c083dd7299f3cf37db9b3f4 [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/alsa/stream_mixer_alsa.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chromecast/media/cma/backend/alsa/mock_alsa_wrapper.h"
#include "media/audio/audio_device_description.h"
#include "media/base/audio_bus.h"
#include "media/base/vector_math.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
namespace chromecast {
namespace media {
namespace {
// Testing constants that are common to multiple test cases.
const size_t kBytesPerSample = sizeof(int32_t);
const int kNumChannels = 2;
const int kTestMaxReadSize = 4096;
// kTestSamplesPerSecond needs to be higher than kLowSampleRateCutoff for the
// mixer to use it.
const int kTestSamplesPerSecond = 54321;
// 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-bit
// 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,
}
};
// 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(kTestData[index], frames, kBytesPerSample);
return data;
}
class MockInputQueue : public StreamMixerAlsa::InputQueue {
public:
explicit MockInputQueue(int samples_per_second,
const std::string& device_id =
::media::AudioDeviceDescription::kDefaultDeviceId)
: paused_(true),
samples_per_second_(samples_per_second),
max_read_size_(kTestMaxReadSize),
multiplier_(1.0),
primary_(true),
deleting_(false),
device_id_(device_id),
filter_group_(nullptr) {
ON_CALL(*this, GetResampledData(_, _)).WillByDefault(
testing::Invoke(this, &MockInputQueue::DoGetResampledData));
ON_CALL(*this, VolumeScaleAccumulate(_, _, _, _)).WillByDefault(
testing::Invoke(this, &MockInputQueue::DoVolumeScaleAccumulate));
ON_CALL(*this, PrepareToDelete(_)).WillByDefault(
testing::Invoke(this, &MockInputQueue::DoPrepareToDelete));
}
~MockInputQueue() override {}
bool paused() const { return paused_; }
// StreamMixerAlsa::InputQueue implementation:
int input_samples_per_second() const override { return samples_per_second_; }
bool primary() const override { return primary_; }
bool IsDeleting() const override { return deleting_; }
MOCK_METHOD1(Initialize,
void(const MediaPipelineBackendAlsa::RenderingDelay&
mixer_rendering_delay));
std::string device_id() const override { return device_id_; }
void set_filter_group(FilterGroup* group) override { filter_group_ = group; }
FilterGroup* filter_group() override { return filter_group_; }
int MaxReadSize() override { return max_read_size_; }
MOCK_METHOD2(GetResampledData, void(::media::AudioBus* dest, int frames));
MOCK_METHOD4(
VolumeScaleAccumulate,
void(bool repeat_transition, const float* src, int frames, float* dest));
MOCK_METHOD0(OnSkipped, void());
MOCK_METHOD1(AfterWriteFrames,
void(const MediaPipelineBackendAlsa::RenderingDelay&
mixer_rendering_delay));
MOCK_METHOD1(SignalError, void(StreamMixerAlsaInput::MixerError error));
MOCK_METHOD1(PrepareToDelete, void(const OnReadyToDeleteCb& delete_cb));
// Setters and getters for test control.
void SetPaused(bool paused) { paused_ = paused; }
void SetMaxReadSize(int max_read_size) { max_read_size_ = max_read_size; }
void SetData(std::unique_ptr<::media::AudioBus> data) {
CHECK(!data_);
data_ = std::move(data);
max_read_size_ = data_->frames();
}
void SetVolumeMultiplier(float multiplier) {
CHECK(multiplier >= 0.0 && multiplier <= 1.0);
multiplier_ = multiplier;
}
void SetPrimary(bool primary) { primary_ = primary; }
const ::media::AudioBus& data() {
CHECK(data_);
return *data_;
}
float multiplier() const { return multiplier_; }
private:
void DoGetResampledData(::media::AudioBus* dest, int frames) {
CHECK(dest);
CHECK_GE(dest->frames(), frames);
if (data_) {
data_->CopyPartialFramesTo(0, frames, 0, dest);
} else {
dest->ZeroFramesPartial(0, frames);
}
}
void DoVolumeScaleAccumulate(bool repeat_transition,
const float* src,
int frames,
float* dest) {
CHECK(src);
CHECK(dest);
CHECK(multiplier_ >= 0.0 && multiplier_ <= 1.0);
::media::vector_math::FMAC(src, multiplier_, frames, dest);
}
void DoPrepareToDelete(const OnReadyToDeleteCb& delete_cb) {
deleting_ = true;
delete_cb.Run(this);
}
bool paused_;
int samples_per_second_;
int max_read_size_;
float multiplier_;
bool primary_;
bool deleting_;
const std::string device_id_;
FilterGroup* filter_group_;
std::unique_ptr<::media::AudioBus> data_;
DISALLOW_COPY_AND_ASSIGN(MockInputQueue);
};
// Given |inputs|, returns mixed audio data according to the mixing method used
// by the mixer.
std::unique_ptr<::media::AudioBus> GetMixedAudioData(
const std::vector<testing::StrictMock<MockInputQueue>*>& inputs) {
int read_size = std::numeric_limits<int>::max();
for (auto* input : inputs) {
CHECK(input);
read_size = std::min(input->MaxReadSize(), 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)
*result += *(input->data().channel(c) + f) * input->multiplier();
// 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(
testing::StrictMock<MockInputQueue>* input) {
return GetMixedAudioData(
std::vector<testing::StrictMock<MockInputQueue>*>(1, input));
}
// 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)
ASSERT_FLOAT_EQ(*expected_data++, *actual_data++) << c << " " << f;
}
}
} // namespace
class StreamMixerAlsaTest : public testing::Test {
protected:
StreamMixerAlsaTest()
: message_loop_(new base::MessageLoop()),
mock_alsa_(new testing::NiceMock<MockAlsaWrapper>()) {
StreamMixerAlsa::MakeSingleThreadedForTest();
StreamMixerAlsa::Get()->SetAlsaWrapperForTest(base::WrapUnique(mock_alsa_));
}
~StreamMixerAlsaTest() override {
StreamMixerAlsa::Get()->ClearInputsForTest();
StreamMixerAlsa::Get()->SetAlsaWrapperForTest(nullptr);
}
MockAlsaWrapper* mock_alsa() { return mock_alsa_; }
private:
const std::unique_ptr<base::MessageLoop> message_loop_;
testing::NiceMock<MockAlsaWrapper>* mock_alsa_;
DISALLOW_COPY_AND_ASSIGN(StreamMixerAlsaTest);
};
TEST_F(StreamMixerAlsaTest, AddSingleInput) {
auto* input = new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond);
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
EXPECT_CALL(*input, Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(input));
EXPECT_EQ(StreamMixerAlsa::kStateNormalPlayback, mixer->state());
}
TEST_F(StreamMixerAlsaTest, AddMultipleInputs) {
auto* input1 = new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond);
auto* input2 =
new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond * 2);
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
EXPECT_CALL(*input1, Initialize(_)).Times(1);
EXPECT_CALL(*input2, Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(input1));
mixer->AddInput(base::WrapUnique(input2));
// The mixer should be ready to play, and should sample to the initial
// sample rate.
EXPECT_EQ(kTestSamplesPerSecond, mixer->output_samples_per_second());
EXPECT_EQ(StreamMixerAlsa::kStateNormalPlayback, mixer->state());
}
TEST_F(StreamMixerAlsaTest, RemoveInput) {
std::vector<testing::StrictMock<MockInputQueue>*> inputs;
const int kNumInputs = 3;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(new testing::StrictMock<MockInputQueue>(
kTestSamplesPerSecond * (i + 1)));
}
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(inputs[i]));
}
EXPECT_EQ(StreamMixerAlsa::kStateNormalPlayback, mixer->state());
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], PrepareToDelete(_)).Times(1);
mixer->RemoveInput(inputs[i]);
}
// Need to wait for the removal task (it is always posted).
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(mixer->empty());
EXPECT_EQ(StreamMixerAlsa::kStateNormalPlayback, mixer->state());
}
TEST_F(StreamMixerAlsaTest, WriteFrames) {
std::vector<testing::StrictMock<MockInputQueue>*> inputs;
const int kNumInputs = 3;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(
new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond));
inputs.back()->SetPaused(false);
}
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(inputs[i]));
}
ASSERT_EQ(StreamMixerAlsa::kStateNormalPlayback, mixer->state());
// The mixer should pull data from all streams, using the smallest
// MaxReadSize provided by any of the channels.
// TODO(slan): Check that the proper number of frames is pulled.
ASSERT_EQ(3u, inputs.size());
inputs[0]->SetMaxReadSize(1024);
inputs[1]->SetMaxReadSize(512);
inputs[2]->SetMaxReadSize(2048);
for (auto* input : inputs) {
EXPECT_CALL(*input, GetResampledData(_, 512)).Times(1);
EXPECT_CALL(*input, VolumeScaleAccumulate(_, _, 512, _))
.Times(kNumChannels);
EXPECT_CALL(*input, AfterWriteFrames(_)).Times(1);
}
// TODO(slan): Verify that the data is mixed properly with math.
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, 512)).Times(1);
mixer->WriteFramesForTest();
// Make two of these streams non-primary, and exhaust a non-primary stream.
// All non-empty streams shall be polled for data and the mixer shall write
// to ALSA.
inputs[1]->SetPrimary(false);
inputs[1]->SetMaxReadSize(0);
EXPECT_CALL(*inputs[1], OnSkipped());
inputs[2]->SetPrimary(false);
for (auto* input : inputs) {
if (input != inputs[1]) {
EXPECT_CALL(*input, GetResampledData(_, 1024)).Times(1);
EXPECT_CALL(*input, VolumeScaleAccumulate(_, _, 1024, _))
.Times(kNumChannels);
}
EXPECT_CALL(*input, AfterWriteFrames(_)).Times(1);
}
// Note that the new smallest stream shall dictate the length of the write.
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, 1024)).Times(1);
mixer->WriteFramesForTest();
// Exhaust a primary stream. No streams shall be polled for data, and no
// data shall be written to ALSA.
inputs[0]->SetMaxReadSize(0);
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, _)).Times(0);
mixer->WriteFramesForTest();
}
TEST_F(StreamMixerAlsaTest, OneStreamMixesProperly) {
auto* input = new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond);
input->SetPaused(false);
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
EXPECT_CALL(*input, Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(input));
EXPECT_EQ(StreamMixerAlsa::kStateNormalPlayback, mixer->state());
// Populate the stream with data.
const int kNumFrames = 32;
input->SetData(GetTestData(0));
ASSERT_EQ(mock_alsa()->data().size(), 0u);
// Write the stream to ALSA.
EXPECT_CALL(*input, GetResampledData(_, kNumFrames));
EXPECT_CALL(*input, VolumeScaleAccumulate(_, _, kNumFrames, _))
.Times(kNumChannels);
EXPECT_CALL(*input, AfterWriteFrames(_));
mixer->WriteFramesForTest();
// Get the actual stream rendered to ALSA, and compare it against the
// expected stream. The stream should match exactly.
auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
ASSERT_GT(mock_alsa()->data().size(), 0u);
actual->FromInterleaved(
&(mock_alsa()->data()[0]), kNumFrames, kBytesPerSample);
CompareAudioData(input->data(), *actual);
}
TEST_F(StreamMixerAlsaTest, OneStreamIsScaledDownProperly) {
auto* input = new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond);
input->SetPaused(false);
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
EXPECT_CALL(*input, Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(input));
EXPECT_EQ(StreamMixerAlsa::kStateNormalPlayback, mixer->state());
// Populate the stream with data.
const int kNumFrames = 32;
ASSERT_EQ(sizeof(kTestData[0]), kNumChannels * kNumFrames * kBytesPerSample);
auto data = GetTestData(0);
input->SetData(std::move(data));
// Set a volume multiplier on the stream.
input->SetVolumeMultiplier(0.75);
// Write the stream to ALSA.
EXPECT_CALL(*input, GetResampledData(_, kNumFrames));
EXPECT_CALL(*input, VolumeScaleAccumulate(_, _, kNumFrames, _))
.Times(kNumChannels);
EXPECT_CALL(*input, AfterWriteFrames(_));
mixer->WriteFramesForTest();
// Check that the retrieved stream is scaled correctly.
auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
actual->FromInterleaved(
&(mock_alsa()->data()[0]), kNumFrames, kBytesPerSample);
auto expected = GetMixedAudioData(input);
CompareAudioData(*expected, *actual);
}
TEST_F(StreamMixerAlsaTest, TwoUnscaledStreamsMixProperly) {
// Create a group of input streams.
std::vector<testing::StrictMock<MockInputQueue>*> inputs;
const int kNumInputs = 2;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(
new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond));
inputs.back()->SetPaused(false);
}
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(inputs[i]));
}
// Poll the inputs for data.
const int kNumFrames = 32;
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
EXPECT_CALL(*inputs[i], GetResampledData(_, kNumFrames));
EXPECT_CALL(*inputs[i], VolumeScaleAccumulate(_, _, kNumFrames, _))
.Times(kNumChannels);
EXPECT_CALL(*inputs[i], AfterWriteFrames(_));
}
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, kNumFrames)).Times(1);
mixer->WriteFramesForTest();
// Mix the inputs manually.
auto expected = GetMixedAudioData(inputs);
// Get the actual stream rendered to ALSA, and compare it against the
// expected stream. The stream should match exactly.
auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
actual->FromInterleaved(&(mock_alsa()->data()[0]), kNumFrames,
kBytesPerSample);
CompareAudioData(*expected, *actual);
}
TEST_F(StreamMixerAlsaTest, TwoUnscaledStreamsWithDifferentIdsMixProperly) {
// Create a group of input streams.
std::vector<testing::StrictMock<MockInputQueue>*> inputs;
inputs.push_back(new testing::StrictMock<MockInputQueue>(
kTestSamplesPerSecond,
::media::AudioDeviceDescription::kDefaultDeviceId));
inputs.back()->SetPaused(false);
inputs.push_back(new testing::StrictMock<MockInputQueue>(
kTestSamplesPerSecond,
::media::AudioDeviceDescription::kCommunicationsDeviceId));
inputs.back()->SetPaused(false);
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(inputs[i]));
}
// Poll the inputs for data.
const int kNumFrames = 32;
for (size_t i = 0; i < inputs.size(); ++i) {
inputs[i]->SetData(GetTestData(i));
EXPECT_CALL(*inputs[i], GetResampledData(_, kNumFrames));
EXPECT_CALL(*inputs[i], VolumeScaleAccumulate(_, _, kNumFrames, _))
.Times(kNumChannels);
EXPECT_CALL(*inputs[i], AfterWriteFrames(_));
}
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, kNumFrames)).Times(1);
mixer->WriteFramesForTest();
// Mix the inputs manually.
auto expected = GetMixedAudioData(inputs);
// Get the actual stream rendered to ALSA, and compare it against the
// expected stream. The stream should match exactly.
auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
actual->FromInterleaved(
&(mock_alsa()->data()[0]), kNumFrames, kBytesPerSample);
CompareAudioData(*expected, *actual);
}
TEST_F(StreamMixerAlsaTest, TwoUnscaledStreamsMixProperlyWithEdgeCases) {
// Create a group of input streams.
std::vector<testing::StrictMock<MockInputQueue>*> inputs;
const int kNumInputs = 2;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(
new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond));
inputs.back()->SetPaused(false);
}
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(inputs[i]));
}
// 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 = 3;
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,
};
for (size_t i = 0; i < inputs.size(); ++i) {
auto test_data = ::media::AudioBus::Create(kNumChannels, kNumFrames);
test_data->FromInterleaved(kEdgeData[i], kNumFrames, kBytesPerSample);
inputs[i]->SetData(std::move(test_data));
EXPECT_CALL(*inputs[i], GetResampledData(_, kNumFrames));
EXPECT_CALL(*inputs[i], VolumeScaleAccumulate(_, _, kNumFrames, _))
.Times(kNumChannels);
EXPECT_CALL(*inputs[i], AfterWriteFrames(_));
}
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, kNumFrames)).Times(1);
mixer->WriteFramesForTest();
// Use the hand-calculated results above.
auto expected = ::media::AudioBus::Create(kNumChannels, kNumFrames);
expected->FromInterleaved(kResult, kNumFrames, kBytesPerSample);
// Get the actual stream rendered to ALSA, and compare it against the
// expected stream. The stream should match exactly.
auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
actual->FromInterleaved(
&(mock_alsa()->data()[0]), kNumFrames, kBytesPerSample);
CompareAudioData(*expected, *actual);
}
TEST_F(StreamMixerAlsaTest, WriteBuffersOfVaryingLength) {
auto* input = new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond);
input->SetPaused(false);
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
EXPECT_CALL(*input, Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(input));
EXPECT_EQ(StreamMixerAlsa::kStateNormalPlayback, mixer->state());
// The input stream will provide buffers of several different lengths.
input->SetMaxReadSize(7);
EXPECT_CALL(*input, GetResampledData(_, 7));
EXPECT_CALL(*input, VolumeScaleAccumulate(_, _, 7, _)).Times(kNumChannels);
EXPECT_CALL(*input, AfterWriteFrames(_));
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, 7)).Times(1);
mixer->WriteFramesForTest();
input->SetMaxReadSize(100);
EXPECT_CALL(*input, GetResampledData(_, 100));
EXPECT_CALL(*input, VolumeScaleAccumulate(_, _, 100, _)).Times(kNumChannels);
EXPECT_CALL(*input, AfterWriteFrames(_));
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, 100)).Times(1);
mixer->WriteFramesForTest();
input->SetMaxReadSize(32);
EXPECT_CALL(*input, GetResampledData(_, 32));
EXPECT_CALL(*input, VolumeScaleAccumulate(_, _, 32, _)).Times(kNumChannels);
EXPECT_CALL(*input, AfterWriteFrames(_));
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, 32)).Times(1);
mixer->WriteFramesForTest();
input->SetMaxReadSize(1024);
EXPECT_CALL(*input, GetResampledData(_, 1024));
EXPECT_CALL(*input, VolumeScaleAccumulate(_, _, 1024, _)).Times(kNumChannels);
EXPECT_CALL(*input, AfterWriteFrames(_));
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, 1024)).Times(1);
mixer->WriteFramesForTest();
}
TEST_F(StreamMixerAlsaTest, StuckStreamWithoutUnderrun) {
// Create a group of input streams.
std::vector<testing::StrictMock<MockInputQueue>*> inputs;
const int kNumInputs = 2;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(
new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond));
inputs.back()->SetMaxReadSize(0);
inputs.back()->SetPaused(false);
}
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(inputs[i]));
}
// Poll the inputs for data. Should not pull any data since one input has none
// to give.
inputs[0]->SetData(GetTestData(0));
EXPECT_CALL(*inputs[0], GetResampledData(_, _)).Times(0);
EXPECT_CALL(*inputs[0], VolumeScaleAccumulate(_, _, _, _)).Times(0);
EXPECT_CALL(*inputs[0], AfterWriteFrames(_)).Times(0);
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, _)).Times(0);
mixer->WriteFramesForTest();
}
TEST_F(StreamMixerAlsaTest, StuckStreamWithUnderrun) {
// Create a group of input streams.
std::vector<testing::StrictMock<MockInputQueue>*> inputs;
const int kNumInputs = 2;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(
new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond));
inputs.back()->SetMaxReadSize(0);
inputs.back()->SetPaused(false);
}
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(inputs[i]));
}
mock_alsa()->set_state(SND_PCM_STATE_XRUN);
// Poll the inputs for data. The first input will provide data (since the
// output is in an underrun condition); the second input can't provide any
// data, but AfterWriteFrames() will still be called on it so that it has the
// correct rendering delay.
const int kNumFrames = 32;
inputs[0]->SetData(GetTestData(0));
EXPECT_CALL(*inputs[0], GetResampledData(_, kNumFrames));
EXPECT_CALL(*inputs[0], VolumeScaleAccumulate(_, _, kNumFrames, _))
.Times(kNumChannels);
EXPECT_CALL(*inputs[0], AfterWriteFrames(_));
EXPECT_CALL(*inputs[1], GetResampledData(_, _)).Times(0);
EXPECT_CALL(*inputs[1], VolumeScaleAccumulate(_, _, kNumFrames, _)).Times(0);
EXPECT_CALL(*inputs[1], OnSkipped());
EXPECT_CALL(*inputs[1], AfterWriteFrames(_));
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, kNumFrames)).Times(1);
mixer->WriteFramesForTest();
}
TEST_F(StreamMixerAlsaTest, StuckStreamWithLowBuffer) {
// Create a group of input streams.
std::vector<testing::StrictMock<MockInputQueue>*> inputs;
const int kNumInputs = 2;
for (int i = 0; i < kNumInputs; ++i) {
inputs.push_back(
new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond));
inputs.back()->SetMaxReadSize(0);
inputs.back()->SetPaused(false);
}
StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
for (size_t i = 0; i < inputs.size(); ++i) {
EXPECT_CALL(*inputs[i], Initialize(_)).Times(1);
mixer->AddInput(base::WrapUnique(inputs[i]));
}
mock_alsa()->set_avail(4086);
// Poll the inputs for data. The first input will provide data (since the
// output is in an low buffer condition); the second input can't provide any
// data, but AfterWriteFrames() will still be called on it so that it has the
// correct rendering delay.
const int kNumFrames = 32;
inputs[0]->SetData(GetTestData(0));
EXPECT_CALL(*inputs[0], GetResampledData(_, kNumFrames));
EXPECT_CALL(*inputs[0], VolumeScaleAccumulate(_, _, kNumFrames, _))
.Times(kNumChannels);
EXPECT_CALL(*inputs[0], AfterWriteFrames(_));
EXPECT_CALL(*inputs[1], GetResampledData(_, _)).Times(0);
EXPECT_CALL(*inputs[1], VolumeScaleAccumulate(_, _, _, _)).Times(0);
EXPECT_CALL(*inputs[1], OnSkipped());
EXPECT_CALL(*inputs[1], AfterWriteFrames(_));
EXPECT_CALL(*mock_alsa(), PcmWritei(_, _, kNumFrames)).Times(1);
mixer->WriteFramesForTest();
}
} // namespace media
} // namespace chromecast