blob: 76ffc17adceed1a0e2593769138cf79b459dc659 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_manager_base.h"
#include "media/audio/audio_output_dispatcher_impl.h"
#include "media/audio/audio_output_proxy.h"
#include "media/audio/audio_output_resampler.h"
#include "media/audio/fake_audio_log_factory.h"
#include "media/audio/fake_audio_output_stream.h"
#include "media/audio/test_audio_thread.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AllOf;
using ::testing::DoAll;
using ::testing::Field;
using ::testing::Mock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SetArrayArgument;
using media::AudioBus;
using media::AudioInputStream;
using media::AudioManager;
using media::AudioManagerBase;
using media::AudioOutputDispatcher;
using media::AudioOutputProxy;
using media::AudioOutputStream;
using media::AudioParameters;
using media::FakeAudioOutputStream;
using media::TestAudioThread;
namespace {
static const int kTestCloseDelayMs = 10;
// Delay between callbacks to AudioSourceCallback::OnMoreData.
static const int kOnMoreDataCallbackDelayMs = 10;
// Let start run long enough for many OnMoreData callbacks to occur.
static const int kStartRunTimeMs = kOnMoreDataCallbackDelayMs * 10;
// Dummy function.
std::unique_ptr<media::AudioDebugRecorder> RegisterDebugRecording(
const media::AudioParameters& params) {
return nullptr;
}
class MockAudioOutputStream : public AudioOutputStream {
public:
MockAudioOutputStream(AudioManagerBase* manager,
const AudioParameters& params)
: start_called_(false),
stop_called_(false),
params_(params),
fake_output_stream_(
FakeAudioOutputStream::MakeFakeStream(manager, params_)) {
}
void Start(AudioSourceCallback* callback) override {
start_called_ = true;
fake_output_stream_->Start(callback);
}
void Stop() override {
stop_called_ = true;
fake_output_stream_->Stop();
}
~MockAudioOutputStream() override = default;
bool start_called() { return start_called_; }
bool stop_called() { return stop_called_; }
MOCK_METHOD0(Open, bool());
MOCK_METHOD1(SetVolume, void(double volume));
MOCK_METHOD1(GetVolume, void(double* volume));
MOCK_METHOD0(Close, void());
MOCK_METHOD0(Flush, void());
private:
bool start_called_;
bool stop_called_;
AudioParameters params_;
std::unique_ptr<AudioOutputStream> fake_output_stream_;
};
class CallbackExposingMockOutputStream : public AudioOutputStream {
public:
CallbackExposingMockOutputStream() = default;
void Start(AudioSourceCallback* callback) override { callback_ = callback; }
void Stop() override { callback_.reset(); }
~CallbackExposingMockOutputStream() override = default;
MOCK_METHOD0(Open, bool());
MOCK_METHOD1(SetVolume, void(double volume));
MOCK_METHOD1(GetVolume, void(double* volume));
MOCK_METHOD0(Close, void());
MOCK_METHOD0(Flush, void());
absl::optional<AudioOutputStream::AudioSourceCallback*> GetCallback() {
return callback_;
}
private:
absl::optional<AudioOutputStream::AudioSourceCallback*> callback_;
};
class MockAudioManager : public AudioManagerBase {
public:
MockAudioManager()
: AudioManagerBase(std::make_unique<TestAudioThread>(),
&fake_audio_log_factory_) {}
~MockAudioManager() override { Shutdown(); }
MOCK_METHOD3(MakeAudioOutputStream,
AudioOutputStream*(const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback));
MOCK_METHOD2(MakeAudioOutputStreamProxy, AudioOutputStream*(
const AudioParameters& params,
const std::string& device_id));
MOCK_METHOD3(MakeAudioInputStream,
AudioInputStream*(const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback));
MOCK_METHOD0(GetTaskRunner, scoped_refptr<base::SingleThreadTaskRunner>());
MOCK_METHOD0(GetWorkerTaskRunner,
scoped_refptr<base::SingleThreadTaskRunner>());
MOCK_METHOD0(GetName, const char*());
MOCK_METHOD2(MakeLinearOutputStream,
AudioOutputStream*(const AudioParameters& params,
const LogCallback& log_callback));
MOCK_METHOD3(MakeLowLatencyOutputStream,
AudioOutputStream*(const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback));
MOCK_METHOD3(MakeLinearInputStream,
AudioInputStream*(const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback));
MOCK_METHOD3(MakeLowLatencyInputStream,
AudioInputStream*(const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback));
protected:
MOCK_METHOD0(HasAudioOutputDevices, bool());
MOCK_METHOD0(HasAudioInputDevices, bool());
MOCK_METHOD1(GetAudioInputDeviceNames,
void(media::AudioDeviceNames* device_name));
MOCK_METHOD2(GetPreferredOutputStreamParameters, AudioParameters(
const std::string& device_id, const AudioParameters& params));
private:
media::FakeAudioLogFactory fake_audio_log_factory_;
};
class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
public:
int OnMoreData(base::TimeDelta /* delay */,
base::TimeTicks /* delay_timestamp */,
const media::AudioGlitchInfo& glitch_info,
AudioBus* dest) override {
cumulative_glitch_info_ += glitch_info;
dest->Zero();
return dest->frames();
}
MOCK_METHOD1(OnError, void(ErrorType));
media::AudioGlitchInfo cumulative_glitch_info() {
return cumulative_glitch_info_;
}
private:
media::AudioGlitchInfo cumulative_glitch_info_;
};
} // namespace
namespace media {
class AudioOutputProxyTest : public testing::Test {
protected:
void SetUp() override {
// Use a low sample rate and large buffer size when testing otherwise the
// FakeAudioOutputStream will keep the message loop busy indefinitely; i.e.,
// RunUntilIdle() will never terminate.
params_ = AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
ChannelLayoutConfig::Stereo(), 8000, 2048);
InitDispatcher(base::Milliseconds(kTestCloseDelayMs));
}
void TearDown() override {
// This is necessary to free all proxy objects that have been
// closed by the test.
base::RunLoop().RunUntilIdle();
}
virtual void InitDispatcher(base::TimeDelta close_delay) {
dispatcher_impl_ = std::make_unique<AudioOutputDispatcherImpl>(
&manager(), params_, std::string(), close_delay);
}
virtual void OnStart() {}
MockAudioManager& manager() {
return manager_;
}
void WaitForCloseTimer(MockAudioOutputStream* stream) {
base::RunLoop run_loop;
EXPECT_CALL(*stream, Close())
.WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
run_loop.Run();
}
void CloseAndWaitForCloseTimer(AudioOutputProxy* proxy,
MockAudioOutputStream* stream) {
// Close the stream and verify it doesn't happen immediately.
proxy->Close();
Mock::VerifyAndClear(stream);
// Wait for the actual close event to come from the close timer.
WaitForCloseTimer(stream);
}
// Basic Open() and Close() test.
void OpenAndClose(AudioOutputDispatcher* dispatcher) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open())
.WillOnce(Return(true));
AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
CloseAndWaitForCloseTimer(proxy, &stream);
}
// Creates a stream, and then calls Start() and Stop().
void StartAndStop(AudioOutputDispatcher* dispatcher) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open())
.WillOnce(Return(true));
EXPECT_CALL(stream, SetVolume(_))
.Times(1);
AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
proxy->Start(&callback_);
OnStart();
proxy->Stop();
CloseAndWaitForCloseTimer(proxy, &stream);
EXPECT_TRUE(stream.stop_called());
EXPECT_TRUE(stream.start_called());
}
// Verify that the stream is closed after Stop() is called.
void CloseAfterStop(AudioOutputDispatcher* dispatcher) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open())
.WillOnce(Return(true));
EXPECT_CALL(stream, SetVolume(_))
.Times(1);
AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
proxy->Start(&callback_);
OnStart();
proxy->Stop();
// Wait for the close timer to fire after StopStream().
WaitForCloseTimer(&stream);
proxy->Close();
EXPECT_TRUE(stream.stop_called());
EXPECT_TRUE(stream.start_called());
}
// Create two streams, but don't start them. Only one device must be opened.
void TwoStreams(AudioOutputDispatcher* dispatcher) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open())
.WillOnce(Return(true));
AudioOutputProxy* proxy1 = dispatcher->CreateStreamProxy();
AudioOutputProxy* proxy2 = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy1->Open());
EXPECT_TRUE(proxy2->Open());
proxy1->Close();
CloseAndWaitForCloseTimer(proxy2, &stream);
EXPECT_FALSE(stream.stop_called());
EXPECT_FALSE(stream.start_called());
}
// Open() method failed.
void OpenFailed(AudioOutputDispatcher* dispatcher) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open())
.WillOnce(Return(false));
EXPECT_CALL(stream, Close())
.Times(1);
AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
EXPECT_FALSE(proxy->Open());
proxy->Close();
EXPECT_FALSE(stream.stop_called());
EXPECT_FALSE(stream.start_called());
}
void CreateAndWait(AudioOutputDispatcher* dispatcher) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open())
.WillOnce(Return(true));
AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
WaitForCloseTimer(&stream);
proxy->Close();
EXPECT_FALSE(stream.stop_called());
EXPECT_FALSE(stream.start_called());
}
void OneStream_TwoPlays(AudioOutputDispatcher* dispatcher) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open())
.WillOnce(Return(true));
EXPECT_CALL(stream, SetVolume(_))
.Times(2);
AudioOutputProxy* proxy1 = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy1->Open());
proxy1->Start(&callback_);
OnStart();
proxy1->Stop();
// The stream should now be idle and get reused by |proxy2|.
AudioOutputProxy* proxy2 = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy2->Open());
proxy2->Start(&callback_);
OnStart();
proxy2->Stop();
proxy1->Close();
CloseAndWaitForCloseTimer(proxy2, &stream);
EXPECT_TRUE(stream.stop_called());
EXPECT_TRUE(stream.start_called());
}
void TwoStreams_BothPlaying(AudioOutputDispatcher* dispatcher) {
MockAudioOutputStream stream1(&manager_, params_);
MockAudioOutputStream stream2(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream1))
.WillOnce(Return(&stream2));
EXPECT_CALL(stream1, Open())
.WillOnce(Return(true));
EXPECT_CALL(stream1, SetVolume(_))
.Times(1);
EXPECT_CALL(stream2, Open())
.WillOnce(Return(true));
EXPECT_CALL(stream2, SetVolume(_))
.Times(1);
AudioOutputProxy* proxy1 = dispatcher->CreateStreamProxy();
AudioOutputProxy* proxy2 = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy1->Open());
EXPECT_TRUE(proxy2->Open());
proxy1->Start(&callback_);
proxy2->Start(&callback_);
OnStart();
proxy1->Stop();
CloseAndWaitForCloseTimer(proxy1, &stream1);
proxy2->Stop();
CloseAndWaitForCloseTimer(proxy2, &stream2);
EXPECT_TRUE(stream1.stop_called());
EXPECT_TRUE(stream1.start_called());
EXPECT_TRUE(stream2.stop_called());
EXPECT_TRUE(stream2.start_called());
}
void StartFailed(AudioOutputDispatcher* dispatcher) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open())
.WillOnce(Return(true));
AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
WaitForCloseTimer(&stream);
// |stream| is closed at this point. Start() should reopen it again.
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.Times(2)
.WillRepeatedly(Return(reinterpret_cast<AudioOutputStream*>(NULL)));
EXPECT_CALL(callback_, OnError(_)).Times(2);
proxy->Start(&callback_);
// Double Start() in the error case should be allowed since it's possible a
// callback may not have had time to process the OnError() in between.
proxy->Stop();
proxy->Start(&callback_);
Mock::VerifyAndClear(&callback_);
proxy->Close();
}
void DispatcherDestroyed_BeforeOpen(
std::unique_ptr<AudioOutputDispatcher> dispatcher) {
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _)).Times(0);
AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
dispatcher.reset();
EXPECT_FALSE(proxy->Open());
proxy->Close();
}
void DispatcherDestroyed_BeforeStart(
std::unique_ptr<AudioOutputDispatcher> dispatcher) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open()).WillOnce(Return(true));
EXPECT_CALL(stream, Close()).Times(1);
AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
EXPECT_CALL(callback_, OnError(_)).Times(1);
dispatcher.reset();
proxy->Start(&callback_);
proxy->Stop();
proxy->Close();
}
void DispatcherDestroyed_BeforeStop(
std::unique_ptr<AudioOutputDispatcher> dispatcher) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open()).WillOnce(Return(true));
EXPECT_CALL(stream, Close()).Times(1);
EXPECT_CALL(stream, SetVolume(_)).Times(1);
AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
proxy->Start(&callback_);
dispatcher.reset();
proxy->Stop();
proxy->Close();
}
void DispatcherDestroyed_AfterStop(
std::unique_ptr<AudioOutputDispatcher> dispatcher) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open()).WillOnce(Return(true));
EXPECT_CALL(stream, Close()).Times(1);
EXPECT_CALL(stream, SetVolume(_)).Times(1);
AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
proxy->Start(&callback_);
proxy->Stop();
dispatcher.reset();
proxy->Close();
}
base::test::SingleThreadTaskEnvironment task_environment_;
MockAudioManager manager_;
std::unique_ptr<AudioOutputDispatcherImpl> dispatcher_impl_;
MockAudioSourceCallback callback_;
AudioParameters params_;
};
class AudioOutputResamplerTest : public AudioOutputProxyTest {
public:
void InitDispatcher(base::TimeDelta close_delay) override {
// Use a low sample rate and large buffer size when testing otherwise the
// FakeAudioOutputStream will keep the message loop busy indefinitely; i.e.,
// RunUntilIdle() will never terminate.
resampler_params_ =
AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Stereo(), 16000, 1024);
resampler_ = std::make_unique<AudioOutputResampler>(
&manager(), params_, resampler_params_, std::string(), close_delay,
base::BindRepeating(&RegisterDebugRecording));
}
void OnStart() override {
// Let Start() run for a bit.
base::RunLoop run_loop;
task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(kStartRunTimeMs));
run_loop.Run();
}
protected:
AudioParameters resampler_params_;
std::unique_ptr<AudioOutputResampler> resampler_;
};
TEST_F(AudioOutputProxyTest, CreateAndClose) {
AudioOutputProxy* proxy = dispatcher_impl_->CreateStreamProxy();
proxy->Close();
}
TEST_F(AudioOutputResamplerTest, CreateAndClose) {
AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
proxy->Close();
}
TEST_F(AudioOutputProxyTest, OpenAndClose) {
OpenAndClose(dispatcher_impl_.get());
}
TEST_F(AudioOutputResamplerTest, OpenAndClose) {
OpenAndClose(resampler_.get());
}
// Create a stream, and verify that it is closed after kTestCloseDelayMs.
// if it doesn't start playing.
TEST_F(AudioOutputProxyTest, CreateAndWait) {
CreateAndWait(dispatcher_impl_.get());
}
// Create a stream, and verify that it is closed after kTestCloseDelayMs.
// if it doesn't start playing.
TEST_F(AudioOutputResamplerTest, CreateAndWait) {
CreateAndWait(resampler_.get());
}
TEST_F(AudioOutputProxyTest, StartAndStop) {
StartAndStop(dispatcher_impl_.get());
}
TEST_F(AudioOutputResamplerTest, StartAndStop) {
StartAndStop(resampler_.get());
}
TEST_F(AudioOutputProxyTest, CloseAfterStop) {
CloseAfterStop(dispatcher_impl_.get());
}
TEST_F(AudioOutputResamplerTest, CloseAfterStop) {
CloseAfterStop(resampler_.get());
}
TEST_F(AudioOutputProxyTest, TwoStreams) {
TwoStreams(dispatcher_impl_.get());
}
TEST_F(AudioOutputResamplerTest, TwoStreams) {
TwoStreams(resampler_.get());
}
// Two streams: verify that second stream is allocated when the first
// starts playing.
TEST_F(AudioOutputProxyTest, OneStream_TwoPlays) {
OneStream_TwoPlays(dispatcher_impl_.get());
}
TEST_F(AudioOutputResamplerTest, OneStream_TwoPlays) {
OneStream_TwoPlays(resampler_.get());
}
// Two streams, both are playing. Dispatcher should not open a third stream.
TEST_F(AudioOutputProxyTest, TwoStreams_BothPlaying) {
TwoStreams_BothPlaying(dispatcher_impl_.get());
}
TEST_F(AudioOutputResamplerTest, TwoStreams_BothPlaying) {
TwoStreams_BothPlaying(resampler_.get());
}
TEST_F(AudioOutputProxyTest, OpenFailed) {
OpenFailed(dispatcher_impl_.get());
}
// Start() method failed.
TEST_F(AudioOutputProxyTest, StartFailed) {
StartFailed(dispatcher_impl_.get());
}
TEST_F(AudioOutputResamplerTest, StartFailed) {
StartFailed(resampler_.get());
}
TEST_F(AudioOutputProxyTest, DispatcherDestroyed_BeforeOpen) {
DispatcherDestroyed_BeforeOpen(std::move(dispatcher_impl_));
}
TEST_F(AudioOutputResamplerTest, DispatcherDestroyed_BeforeOpen) {
DispatcherDestroyed_BeforeOpen(std::move(resampler_));
}
TEST_F(AudioOutputProxyTest, DispatcherDestroyed_BeforeStart) {
DispatcherDestroyed_BeforeStart(std::move(dispatcher_impl_));
}
TEST_F(AudioOutputResamplerTest, DispatcherDestroyed_BeforeStart) {
DispatcherDestroyed_BeforeStart(std::move(resampler_));
}
TEST_F(AudioOutputProxyTest, DispatcherDestroyed_BeforeStop) {
DispatcherDestroyed_BeforeStop(std::move(dispatcher_impl_));
}
TEST_F(AudioOutputResamplerTest, DispatcherDestroyed_BeforeStop) {
DispatcherDestroyed_BeforeStop(std::move(resampler_));
}
TEST_F(AudioOutputProxyTest, DispatcherDestroyed_AfterStop) {
DispatcherDestroyed_AfterStop(std::move(dispatcher_impl_));
}
TEST_F(AudioOutputResamplerTest, DispatcherDestroyed_AfterStop) {
DispatcherDestroyed_AfterStop(std::move(resampler_));
}
TEST_F(AudioOutputProxyTest, DispatcherDeviceChangeClosesIdleStreams) {
// Set close delay so long that it triggers a test timeout if relied upon.
InitDispatcher(base::Seconds(1000));
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open()).WillOnce(Return(true));
AudioOutputProxy* proxy = dispatcher_impl_->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
// Close the stream and verify it doesn't happen immediately.
proxy->Close();
Mock::VerifyAndClear(&stream);
// This should trigger a true close on the stream.
dispatcher_impl_->OnDeviceChange();
base::RunLoop run_loop;
EXPECT_CALL(stream, Close())
.WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
run_loop.Run();
}
// Simulate AudioOutputStream::Create() failure with a low latency stream and
// ensure AudioOutputResampler falls back to the high latency path.
TEST_F(AudioOutputResamplerTest, LowLatencyCreateFailedFallback) {
MockAudioOutputStream stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.Times(2)
.WillOnce(Return(static_cast<AudioOutputStream*>(NULL)))
.WillRepeatedly(Return(&stream));
EXPECT_CALL(stream, Open())
.WillOnce(Return(true));
AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
CloseAndWaitForCloseTimer(proxy, &stream);
}
// Simulate AudioOutputStream::Open() failure with a low latency stream and
// ensure AudioOutputResampler falls back to the high latency path.
TEST_F(AudioOutputResamplerTest, LowLatencyOpenFailedFallback) {
MockAudioOutputStream failed_stream(&manager_, params_);
MockAudioOutputStream okay_stream(&manager_, params_);
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.Times(2)
.WillOnce(Return(&failed_stream))
.WillRepeatedly(Return(&okay_stream));
EXPECT_CALL(failed_stream, Open())
.WillOnce(Return(false));
EXPECT_CALL(failed_stream, Close())
.Times(1);
EXPECT_CALL(okay_stream, Open())
.WillOnce(Return(true));
AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
CloseAndWaitForCloseTimer(proxy, &okay_stream);
}
// Simulate failures to open both the low latency and the fallback high latency
// stream and ensure AudioOutputResampler falls back to a fake stream.
TEST_F(AudioOutputResamplerTest, HighLatencyFallbackFailed) {
MockAudioOutputStream okay_stream(&manager_, params_);
// Only Windows has a high latency output driver that is not the same as the low
// latency path.
#if BUILDFLAG(IS_WIN)
static const int kFallbackCount = 2;
#else
static const int kFallbackCount = 1;
#endif
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.Times(kFallbackCount)
.WillRepeatedly(Return(static_cast<AudioOutputStream*>(NULL)));
// To prevent shared memory issues the sample rate and buffer size should
// match the input stream parameters.
EXPECT_CALL(manager(),
MakeAudioOutputStream(
AllOf(testing::Property(&AudioParameters::format,
AudioParameters::AUDIO_FAKE),
testing::Property(&AudioParameters::sample_rate,
params_.sample_rate()),
testing::Property(&AudioParameters::frames_per_buffer,
params_.frames_per_buffer())),
_, _))
.Times(1)
.WillOnce(Return(&okay_stream));
EXPECT_CALL(okay_stream, Open())
.WillOnce(Return(true));
AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
CloseAndWaitForCloseTimer(proxy, &okay_stream);
}
// Simulate failures to open both the low latency, the fallback high latency
// stream, and the fake audio output stream and ensure AudioOutputResampler
// terminates normally.
TEST_F(AudioOutputResamplerTest, AllFallbackFailed) {
// Only Windows has a high latency output driver that is not the same as the low
// latency path.
#if BUILDFLAG(IS_WIN)
static const int kFallbackCount = 3;
#else
static const int kFallbackCount = 2;
#endif
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.Times(kFallbackCount)
.WillRepeatedly(Return(static_cast<AudioOutputStream*>(NULL)));
AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
EXPECT_FALSE(proxy->Open());
proxy->Close();
}
// Simulate an eventual OpenStream() failure; i.e. successful OpenStream() calls
// eventually followed by one which fails; root cause of http://crbug.com/150619
TEST_F(AudioOutputResamplerTest, LowLatencyOpenEventuallyFails) {
MockAudioOutputStream stream1(&manager_, params_);
MockAudioOutputStream stream2(&manager_, params_);
// Setup the mock such that all three streams are successfully created.
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream1))
.WillOnce(Return(&stream2))
.WillRepeatedly(Return(static_cast<AudioOutputStream*>(NULL)));
// Stream1 should be able to successfully open and start.
EXPECT_CALL(stream1, Open())
.WillOnce(Return(true));
EXPECT_CALL(stream1, SetVolume(_))
.Times(1);
// Stream2 should also be able to successfully open and start.
EXPECT_CALL(stream2, Open())
.WillOnce(Return(true));
EXPECT_CALL(stream2, SetVolume(_))
.Times(1);
// Open and start the first proxy and stream.
AudioOutputProxy* proxy1 = resampler_->CreateStreamProxy();
EXPECT_TRUE(proxy1->Open());
proxy1->Start(&callback_);
OnStart();
// Open and start the second proxy and stream.
AudioOutputProxy* proxy2 = resampler_->CreateStreamProxy();
EXPECT_TRUE(proxy2->Open());
proxy2->Start(&callback_);
OnStart();
// Attempt to open the third stream which should fail.
AudioOutputProxy* proxy3 = resampler_->CreateStreamProxy();
EXPECT_FALSE(proxy3->Open());
proxy3->Close();
// Perform the required Stop()/Close() shutdown dance for each proxy. Under
// the hood each proxy should correctly call CloseStream() if OpenStream()
// succeeded or not.
proxy2->Stop();
CloseAndWaitForCloseTimer(proxy2, &stream2);
proxy1->Stop();
CloseAndWaitForCloseTimer(proxy1, &stream1);
EXPECT_TRUE(stream1.stop_called());
EXPECT_TRUE(stream1.start_called());
EXPECT_TRUE(stream2.stop_called());
EXPECT_TRUE(stream2.start_called());
}
// Simulate failures to open both the low latency and the fallback high latency
// stream and ensure AudioOutputResampler falls back to a fake stream. Ensure
// that after the close delay elapses, opening another stream succeeds with a
// non-fake stream.
TEST_F(AudioOutputResamplerTest, FallbackRecovery) {
MockAudioOutputStream fake_stream(&manager_, params_);
// Trigger the fallback mechanism until a fake output stream is created.
#if BUILDFLAG(IS_WIN)
static const int kFallbackCount = 2;
#else
static const int kFallbackCount = 1;
#endif
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.Times(kFallbackCount)
.WillRepeatedly(Return(static_cast<AudioOutputStream*>(NULL)));
EXPECT_CALL(manager(),
MakeAudioOutputStream(
AllOf(testing::Property(&AudioParameters::format,
AudioParameters::AUDIO_FAKE),
testing::Property(&AudioParameters::sample_rate,
params_.sample_rate()),
testing::Property(&AudioParameters::frames_per_buffer,
params_.frames_per_buffer())),
_, _))
.WillOnce(Return(&fake_stream));
EXPECT_CALL(fake_stream, Open()).WillOnce(Return(true));
AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
CloseAndWaitForCloseTimer(proxy, &fake_stream);
// Once all proxies have been closed, AudioOutputResampler will start the
// reinitialization timer and execute it after the close delay elapses.
base::RunLoop run_loop;
task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(),
base::Milliseconds(2 * kTestCloseDelayMs));
run_loop.Run();
// Verify a non-fake stream can be created.
MockAudioOutputStream real_stream(&manager_, params_);
EXPECT_CALL(manager(),
MakeAudioOutputStream(
testing::Property(&AudioParameters::format,
testing::Ne(AudioParameters::AUDIO_FAKE)),
_, _))
.WillOnce(Return(&real_stream));
// Stream1 should be able to successfully open and start.
EXPECT_CALL(real_stream, Open()).WillOnce(Return(true));
proxy = resampler_->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
CloseAndWaitForCloseTimer(proxy, &real_stream);
}
TEST_F(AudioOutputResamplerTest, PropagatesGlitchInfo) {
CallbackExposingMockOutputStream stream;
EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
.WillOnce(Return(&stream));
EXPECT_CALL(stream, Open()).WillOnce(Return(true));
EXPECT_CALL(stream, SetVolume(_)).Times(1);
AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
EXPECT_TRUE(proxy->Open());
proxy->Start(&callback_);
// Get the callback created by the resampler and send glitch info through it.
CHECK(stream.GetCallback());
AudioOutputStream::AudioSourceCallback* inner_callback =
stream.GetCallback().value();
media::AudioGlitchInfo glitch_info{.duration = base::Seconds(0.1),
.count = 123};
auto dest = AudioBus::Create(resampler_params_);
inner_callback->OnMoreData(base::TimeDelta(), base::TimeTicks(), glitch_info,
dest.get());
EXPECT_EQ(callback_.cumulative_glitch_info(), glitch_info);
inner_callback->OnMoreData(base::TimeDelta(), base::TimeTicks(), {},
dest.get());
EXPECT_EQ(callback_.cumulative_glitch_info(), glitch_info);
proxy->Stop();
proxy->Close();
Mock::VerifyAndClearExpectations(&stream);
base::RunLoop run_loop;
EXPECT_CALL(stream, Close())
.WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
run_loop.Run();
}
} // namespace media