blob: 34e2dda6a75c30f0144a9d7d75b6beac30f7e45b [file] [log] [blame]
// Copyright (c) 2012 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 "media/audio/audio_output_device.h"
#include <stdint.h>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/single_thread_task_runner.h"
#include "base/sync_socket.h"
#include "base/task_runner.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "media/audio/audio_sync_reader.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::CancelableSyncSocket;
using base::UnsafeSharedMemoryRegion;
using base::WritableSharedMemoryMapping;
using base::SyncSocket;
using testing::_;
using testing::DoAll;
using testing::Invoke;
using testing::Return;
using testing::WithArg;
using testing::StrictMock;
using testing::NiceMock;
using testing::NotNull;
using testing::Mock;
namespace media {
namespace {
constexpr char kDefaultDeviceId[] = "";
constexpr char kNonDefaultDeviceId[] = "valid-nondefault-device-id";
constexpr char kUnauthorizedDeviceId[] = "unauthorized-device-id";
constexpr float kAudioData = 0.618;
constexpr base::TimeDelta kAuthTimeout =
base::TimeDelta::FromMilliseconds(10000);
constexpr base::TimeDelta kDelay = base::TimeDelta::FromMicroseconds(123);
constexpr int kFramesSkipped = 456;
constexpr int kFrames = 789;
constexpr int kBitstreamFrames = 101;
constexpr size_t kBitstreamDataSize = 512;
class MockRenderCallback : public AudioRendererSink::RenderCallback {
public:
MockRenderCallback() = default;
~MockRenderCallback() override = default;
MOCK_METHOD4(Render,
int(base::TimeDelta delay,
base::TimeTicks timestamp,
int prior_frames_skipped,
AudioBus* dest));
MOCK_METHOD0(OnRenderError, void());
};
class MockAudioOutputIPC : public AudioOutputIPC {
public:
MockAudioOutputIPC() = default;
~MockAudioOutputIPC() override = default;
MOCK_METHOD3(RequestDeviceAuthorization,
void(AudioOutputIPCDelegate* delegate,
int session_id,
const std::string& device_id));
MOCK_METHOD3(
CreateStream,
void(AudioOutputIPCDelegate* delegate,
const AudioParameters& params,
const base::Optional<base::UnguessableToken>& processing_id));
MOCK_METHOD0(PlayStream, void());
MOCK_METHOD0(PauseStream, void());
MOCK_METHOD0(CloseStream, void());
MOCK_METHOD1(SetVolume, void(double volume));
};
} // namespace.
class AudioOutputDeviceTest : public testing::Test {
public:
AudioOutputDeviceTest();
~AudioOutputDeviceTest() override;
void ReceiveAuthorization(OutputDeviceStatus device_status);
void StartAudioDevice();
void CallOnStreamCreated();
void StopAudioDevice();
void CreateDevice(const std::string& device_id,
base::TimeDelta timeout = kAuthTimeout);
void SetDevice(const std::string& device_id);
MOCK_METHOD1(OnDeviceInfoReceived, void(OutputDeviceInfo));
protected:
base::test::ScopedTaskEnvironment task_env_{
base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME};
AudioParameters default_audio_parameters_;
StrictMock<MockRenderCallback> callback_;
MockAudioOutputIPC* audio_output_ipc_; // owned by audio_device_
scoped_refptr<AudioOutputDevice> audio_device_;
OutputDeviceStatus device_status_;
private:
int CalculateMemorySize();
UnsafeSharedMemoryRegion shared_memory_region_;
WritableSharedMemoryMapping shared_memory_mapping_;
CancelableSyncSocket browser_socket_;
CancelableSyncSocket renderer_socket_;
DISALLOW_COPY_AND_ASSIGN(AudioOutputDeviceTest);
};
AudioOutputDeviceTest::AudioOutputDeviceTest()
: device_status_(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL) {
default_audio_parameters_.Reset(AudioParameters::AUDIO_PCM_LINEAR,
CHANNEL_LAYOUT_STEREO, 48000, 1024);
SetDevice(kDefaultDeviceId);
}
AudioOutputDeviceTest::~AudioOutputDeviceTest() {
audio_device_ = nullptr;
}
void AudioOutputDeviceTest::CreateDevice(const std::string& device_id,
base::TimeDelta timeout) {
// Make sure the previous device is properly cleaned up.
if (audio_device_)
StopAudioDevice();
audio_output_ipc_ = new NiceMock<MockAudioOutputIPC>();
audio_device_ = new AudioOutputDevice(
base::WrapUnique(audio_output_ipc_), task_env_.GetMainThreadTaskRunner(),
AudioSinkParameters(0, device_id), timeout);
}
void AudioOutputDeviceTest::SetDevice(const std::string& device_id) {
CreateDevice(device_id);
EXPECT_CALL(*audio_output_ipc_,
RequestDeviceAuthorization(audio_device_.get(), 0, device_id));
audio_device_->RequestDeviceAuthorization();
task_env_.FastForwardBy(base::TimeDelta());
// Simulate response from browser
OutputDeviceStatus device_status =
(device_id == kUnauthorizedDeviceId)
? OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED
: OUTPUT_DEVICE_STATUS_OK;
ReceiveAuthorization(device_status);
audio_device_->Initialize(default_audio_parameters_,
&callback_);
}
void AudioOutputDeviceTest::ReceiveAuthorization(OutputDeviceStatus status) {
device_status_ = status;
if (device_status_ != OUTPUT_DEVICE_STATUS_OK)
EXPECT_CALL(*audio_output_ipc_, CloseStream());
audio_device_->OnDeviceAuthorized(device_status_, default_audio_parameters_,
kDefaultDeviceId);
task_env_.FastForwardBy(base::TimeDelta());
}
void AudioOutputDeviceTest::StartAudioDevice() {
if (device_status_ == OUTPUT_DEVICE_STATUS_OK)
EXPECT_CALL(*audio_output_ipc_, CreateStream(audio_device_.get(), _, _));
else
EXPECT_CALL(callback_, OnRenderError());
audio_device_->Start();
task_env_.FastForwardBy(base::TimeDelta());
}
void AudioOutputDeviceTest::CallOnStreamCreated() {
const uint32_t kMemorySize =
ComputeAudioOutputBufferSize(default_audio_parameters_);
shared_memory_region_ = base::UnsafeSharedMemoryRegion::Create(kMemorySize);
ASSERT_TRUE(shared_memory_region_.IsValid());
shared_memory_mapping_ = shared_memory_region_.Map();
ASSERT_TRUE(shared_memory_mapping_.IsValid());
memset(shared_memory_mapping_.memory(), 0xff, kMemorySize);
ASSERT_TRUE(CancelableSyncSocket::CreatePair(&browser_socket_,
&renderer_socket_));
// Create duplicates of the handles we pass to AudioOutputDevice since
// ownership will be transferred and AudioOutputDevice is responsible for
// freeing.
SyncSocket::TransitDescriptor audio_device_socket_descriptor;
ASSERT_TRUE(renderer_socket_.PrepareTransitDescriptor(
base::GetCurrentProcessHandle(), &audio_device_socket_descriptor));
base::UnsafeSharedMemoryRegion duplicated_memory_region =
shared_memory_region_.Duplicate();
ASSERT_TRUE(duplicated_memory_region.IsValid());
audio_device_->OnStreamCreated(
std::move(duplicated_memory_region),
SyncSocket::UnwrapHandle(audio_device_socket_descriptor),
/*playing_automatically*/ false);
task_env_.FastForwardBy(base::TimeDelta());
}
void AudioOutputDeviceTest::StopAudioDevice() {
if (device_status_ == OUTPUT_DEVICE_STATUS_OK)
EXPECT_CALL(*audio_output_ipc_, CloseStream());
audio_device_->Stop();
task_env_.FastForwardBy(base::TimeDelta());
}
TEST_F(AudioOutputDeviceTest, Initialize) {
// Tests that the object can be constructed, initialized and destructed
// without having ever been started.
StopAudioDevice();
}
// Calls Start() followed by an immediate Stop() and check for the basic message
// filter messages being sent in that case.
TEST_F(AudioOutputDeviceTest, StartStop) {
StartAudioDevice();
StopAudioDevice();
}
// AudioOutputDevice supports multiple start/stop sequences.
TEST_F(AudioOutputDeviceTest, StartStopStartStop) {
StartAudioDevice();
StopAudioDevice();
StartAudioDevice();
StopAudioDevice();
}
// Simulate receiving OnStreamCreated() prior to processing ShutDownOnIOThread()
// on the IO loop.
TEST_F(AudioOutputDeviceTest, StopBeforeRender) {
StartAudioDevice();
// Call Stop() but don't run the IO loop yet.
audio_device_->Stop();
// Expect us to shutdown IPC but not to render anything despite the stream
// getting created.
EXPECT_CALL(*audio_output_ipc_, CloseStream());
CallOnStreamCreated();
}
// Multiple start/stop with nondefault device
TEST_F(AudioOutputDeviceTest, NonDefaultStartStopStartStop) {
SetDevice(kNonDefaultDeviceId);
StartAudioDevice();
StopAudioDevice();
EXPECT_CALL(*audio_output_ipc_,
RequestDeviceAuthorization(audio_device_.get(), 0, _));
StartAudioDevice();
// Simulate reply from browser
ReceiveAuthorization(OUTPUT_DEVICE_STATUS_OK);
StopAudioDevice();
}
TEST_F(AudioOutputDeviceTest, UnauthorizedDevice) {
SetDevice(kUnauthorizedDeviceId);
StartAudioDevice();
StopAudioDevice();
}
TEST_F(AudioOutputDeviceTest,
StartUnauthorizedDeviceAndStopBeforeErrorFires_NoError) {
SetDevice(kUnauthorizedDeviceId);
audio_device_->Start();
// Don't run the runloop. We stop before |audio_device| gets the
// authorization error, so it's not allowed to dereference |callback_|.
EXPECT_CALL(callback_, OnRenderError()).Times(0);
StopAudioDevice();
}
TEST_F(AudioOutputDeviceTest, AuthorizationFailsBeforeInitialize_NoError) {
// Clear audio device set by fixture.
StopAudioDevice();
audio_output_ipc_ = new NiceMock<MockAudioOutputIPC>();
audio_device_ = new AudioOutputDevice(
base::WrapUnique(audio_output_ipc_), task_env_.GetMainThreadTaskRunner(),
AudioSinkParameters(0, kDefaultDeviceId), kAuthTimeout);
EXPECT_CALL(
*audio_output_ipc_,
RequestDeviceAuthorization(audio_device_.get(), 0, kDefaultDeviceId));
audio_device_->RequestDeviceAuthorization();
audio_device_->Initialize(default_audio_parameters_, &callback_);
task_env_.FastForwardBy(base::TimeDelta());
audio_device_->Stop();
// We've stopped, so accessing |callback_| isn't ok.
EXPECT_CALL(callback_, OnRenderError()).Times(0);
audio_device_->OnDeviceAuthorized(OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED,
default_audio_parameters_,
kDefaultDeviceId);
task_env_.FastForwardBy(base::TimeDelta());
}
TEST_F(AudioOutputDeviceTest, AuthorizationTimedOut) {
CreateDevice(kNonDefaultDeviceId);
EXPECT_CALL(
*audio_output_ipc_,
RequestDeviceAuthorization(audio_device_.get(), 0, kNonDefaultDeviceId));
EXPECT_CALL(*audio_output_ipc_, CloseStream());
// Request authorization; no reply from the browser.
audio_device_->RequestDeviceAuthorization();
// Advance time until we hit the timeout.
task_env_.FastForwardUntilNoTasksRemain();
audio_device_->Stop();
task_env_.FastForwardBy(base::TimeDelta());
}
TEST_F(AudioOutputDeviceTest, GetOutputDeviceInfoAsync_Error) {
CreateDevice(kUnauthorizedDeviceId, base::TimeDelta());
EXPECT_CALL(*audio_output_ipc_,
RequestDeviceAuthorization(audio_device_.get(), 0,
kUnauthorizedDeviceId));
audio_device_->RequestDeviceAuthorization();
audio_device_->GetOutputDeviceInfoAsync(base::BindOnce(
&AudioOutputDeviceTest::OnDeviceInfoReceived, base::Unretained(this)));
task_env_.FastForwardBy(base::TimeDelta());
OutputDeviceInfo info;
constexpr auto kExpectedStatus = OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED;
EXPECT_CALL(*this, OnDeviceInfoReceived(_))
.WillOnce(testing::SaveArg<0>(&info));
ReceiveAuthorization(kExpectedStatus);
task_env_.FastForwardUntilNoTasksRemain();
EXPECT_EQ(kExpectedStatus, info.device_status());
EXPECT_EQ(kUnauthorizedDeviceId, info.device_id());
EXPECT_TRUE(
AudioParameters::UnavailableDeviceParams().Equals(info.output_params()));
audio_device_->Stop();
task_env_.FastForwardBy(base::TimeDelta());
}
TEST_F(AudioOutputDeviceTest, GetOutputDeviceInfoAsync_Okay) {
CreateDevice(kDefaultDeviceId, base::TimeDelta());
EXPECT_CALL(
*audio_output_ipc_,
RequestDeviceAuthorization(audio_device_.get(), 0, kDefaultDeviceId));
audio_device_->RequestDeviceAuthorization();
audio_device_->GetOutputDeviceInfoAsync(base::BindOnce(
&AudioOutputDeviceTest::OnDeviceInfoReceived, base::Unretained(this)));
task_env_.FastForwardBy(base::TimeDelta());
OutputDeviceInfo info;
constexpr auto kExpectedStatus = OUTPUT_DEVICE_STATUS_OK;
EXPECT_CALL(*this, OnDeviceInfoReceived(_))
.WillOnce(testing::SaveArg<0>(&info));
ReceiveAuthorization(kExpectedStatus);
task_env_.FastForwardUntilNoTasksRemain();
EXPECT_EQ(kExpectedStatus, info.device_status());
EXPECT_EQ(kDefaultDeviceId, info.device_id());
EXPECT_TRUE(default_audio_parameters_.Equals(info.output_params()));
audio_device_->Stop();
task_env_.FastForwardBy(base::TimeDelta());
}
namespace {
// This struct collects useful stuff without doing anything magical. It is used
// below, where the test fixture is too inflexible.
struct TestEnvironment {
explicit TestEnvironment(const AudioParameters& params) {
const uint32_t memory_size = ComputeAudioOutputBufferSize(params);
auto shared_memory_region =
base::UnsafeSharedMemoryRegion::Create(memory_size);
auto shared_memory_mapping = shared_memory_region.Map();
CHECK(shared_memory_region.IsValid());
CHECK(shared_memory_mapping.IsValid());
auto browser_socket = std::make_unique<base::CancelableSyncSocket>();
CHECK(CancelableSyncSocket::CreatePair(browser_socket.get(),
&renderer_socket));
reader = std::make_unique<AudioSyncReader>(
/*log callback*/ base::DoNothing(), params,
std::move(shared_memory_region), std::move(shared_memory_mapping),
std::move(browser_socket));
time_stamp = base::TimeTicks::Now();
#if defined(OS_FUCHSIA)
// TODO(https://crbug.com/838367): Fuchsia bots use nested virtualization,
// which can result in unusually long scheduling delays, so allow a longer
// timeout.
reader->set_max_wait_timeout_for_test(
base::TimeDelta::FromMilliseconds(250));
#endif
}
base::CancelableSyncSocket renderer_socket;
StrictMock<MockRenderCallback> callback;
std::unique_ptr<AudioSyncReader> reader;
base::TimeTicks time_stamp;
};
} // namespace
#if defined(ADDRESS_SANITIZER)
// TODO(crbug.com/903696): Flaky, at least on CrOS ASAN.
#define MAYBE_VerifyDataFlow DISABLED_VerifyDataFlow
#else
#define MAYBE_VerifyDataFlow VerifyDataFlow
#endif
TEST_F(AudioOutputDeviceTest, MAYBE_VerifyDataFlow) {
// The test fixture isn't used in this test, but we still have to clean up
// after it.
StopAudioDevice();
auto params = AudioParameters::UnavailableDeviceParams();
params.set_frames_per_buffer(kFrames);
ASSERT_EQ(2, params.channels());
TestEnvironment env(params);
auto* ipc = new MockAudioOutputIPC(); // owned by |audio_device|.
auto audio_device = base::MakeRefCounted<AudioOutputDevice>(
base::WrapUnique(ipc), task_env_.GetMainThreadTaskRunner(),
AudioSinkParameters(0, kDefaultDeviceId), kAuthTimeout);
// Start a stream.
audio_device->RequestDeviceAuthorization();
audio_device->Initialize(params, &env.callback);
audio_device->Start();
EXPECT_CALL(*ipc, RequestDeviceAuthorization(audio_device.get(), 0,
kDefaultDeviceId));
EXPECT_CALL(*ipc, CreateStream(audio_device.get(), _, _));
EXPECT_CALL(*ipc, PlayStream());
task_env_.RunUntilIdle();
Mock::VerifyAndClear(ipc);
audio_device->OnDeviceAuthorized(OUTPUT_DEVICE_STATUS_OK, params,
kDefaultDeviceId);
audio_device->OnStreamCreated(env.reader->TakeSharedMemoryRegion(),
env.renderer_socket.Release(),
/*playing_automatically*/ false);
task_env_.RunUntilIdle();
// At this point, the callback thread should be running. Send some data over
// and verify that it's propagated to |env.callback|. Do it a few times.
auto test_bus = AudioBus::Create(params);
for (int i = 0; i < 10; ++i) {
test_bus->Zero();
EXPECT_CALL(env.callback,
Render(kDelay, env.time_stamp, kFramesSkipped, NotNull()))
.WillOnce(WithArg<3>(Invoke([](AudioBus* renderer_bus) -> int {
// Place some test data in the bus so that we can check that it was
// copied to the browser side.
std::fill_n(renderer_bus->channel(0), renderer_bus->frames(),
kAudioData);
std::fill_n(renderer_bus->channel(1), renderer_bus->frames(),
kAudioData);
return renderer_bus->frames();
})));
env.reader->RequestMoreData(kDelay, env.time_stamp, kFramesSkipped);
env.reader->Read(test_bus.get());
Mock::VerifyAndClear(&env.callback);
for (int i = 0; i < kFrames; ++i) {
EXPECT_EQ(kAudioData, test_bus->channel(0)[i]);
EXPECT_EQ(kAudioData, test_bus->channel(1)[i]);
}
}
audio_device->Stop();
EXPECT_CALL(*ipc, CloseStream());
task_env_.RunUntilIdle();
}
TEST_F(AudioOutputDeviceTest, CreateNondefaultDevice) {
// The test fixture isn't used in this test, but we still have to clean up
// after it.
StopAudioDevice();
auto params = AudioParameters::UnavailableDeviceParams();
params.set_frames_per_buffer(kFrames);
ASSERT_EQ(2, params.channels());
TestEnvironment env(params);
auto* ipc = new MockAudioOutputIPC(); // owned by |audio_device|.
auto audio_device = base::MakeRefCounted<AudioOutputDevice>(
base::WrapUnique(ipc), task_env_.GetMainThreadTaskRunner(),
AudioSinkParameters(0, kNonDefaultDeviceId), kAuthTimeout);
audio_device->RequestDeviceAuthorization();
audio_device->Initialize(params, &env.callback);
audio_device->Start();
EXPECT_CALL(*ipc, RequestDeviceAuthorization(audio_device.get(), 0,
kNonDefaultDeviceId));
EXPECT_CALL(*ipc, CreateStream(audio_device.get(), _, _));
EXPECT_CALL(*ipc, PlayStream());
task_env_.RunUntilIdle();
Mock::VerifyAndClear(ipc);
audio_device->OnDeviceAuthorized(OUTPUT_DEVICE_STATUS_OK, params,
kNonDefaultDeviceId);
audio_device->OnStreamCreated(env.reader->TakeSharedMemoryRegion(),
env.renderer_socket.Release(),
/*playing_automatically*/ false);
audio_device->Stop();
EXPECT_CALL(*ipc, CloseStream());
task_env_.RunUntilIdle();
}
TEST_F(AudioOutputDeviceTest, CreateBitStreamStream) {
// The test fixture isn't used in this test, but we still have to clean up
// after it.
StopAudioDevice();
const int kAudioParameterFrames = 4321;
AudioParameters params(AudioParameters::AUDIO_BITSTREAM_EAC3,
CHANNEL_LAYOUT_STEREO, 48000, kAudioParameterFrames);
TestEnvironment env(params);
auto* ipc = new MockAudioOutputIPC(); // owned by |audio_device|.
auto audio_device = base::MakeRefCounted<AudioOutputDevice>(
base::WrapUnique(ipc), task_env_.GetMainThreadTaskRunner(),
AudioSinkParameters(0, kNonDefaultDeviceId), kAuthTimeout);
// Start a stream.
audio_device->RequestDeviceAuthorization();
audio_device->Initialize(params, &env.callback);
audio_device->Start();
EXPECT_CALL(*ipc, RequestDeviceAuthorization(audio_device.get(), 0,
kNonDefaultDeviceId));
EXPECT_CALL(*ipc, CreateStream(audio_device.get(), _, _));
EXPECT_CALL(*ipc, PlayStream());
task_env_.RunUntilIdle();
Mock::VerifyAndClear(ipc);
audio_device->OnDeviceAuthorized(OUTPUT_DEVICE_STATUS_OK, params,
kNonDefaultDeviceId);
audio_device->OnStreamCreated(env.reader->TakeSharedMemoryRegion(),
env.renderer_socket.Release(),
/*playing_automatically*/ false);
task_env_.RunUntilIdle();
// At this point, the callback thread should be running. Send some data over
// and verify that it's propagated to |env.callback|. Do it a few times.
auto test_bus = AudioBus::Create(params);
for (int i = 0; i < 10; ++i) {
test_bus->Zero();
EXPECT_CALL(env.callback,
Render(kDelay, env.time_stamp, kFramesSkipped, NotNull()))
.WillOnce(WithArg<3>(Invoke([](AudioBus* renderer_bus) -> int {
EXPECT_TRUE(renderer_bus->is_bitstream_format());
// Place some test data in the bus so that we can check that it was
// copied to the browser side.
std::fill_n(renderer_bus->channel(0),
kBitstreamDataSize / sizeof(float), kAudioData);
renderer_bus->SetBitstreamFrames(kBitstreamFrames);
renderer_bus->SetBitstreamDataSize(kBitstreamDataSize);
return renderer_bus->frames();
})));
env.reader->RequestMoreData(kDelay, env.time_stamp, kFramesSkipped);
env.reader->Read(test_bus.get());
Mock::VerifyAndClear(&env.callback);
EXPECT_TRUE(test_bus->is_bitstream_format());
EXPECT_EQ(kBitstreamFrames, test_bus->GetBitstreamFrames());
EXPECT_EQ(kBitstreamDataSize, test_bus->GetBitstreamDataSize());
for (size_t i = 0; i < kBitstreamDataSize / sizeof(float); ++i) {
// Note: if all of these fail, the bots will behave strangely due to the
// large amount of text output. Assert is used to avoid this.
ASSERT_EQ(kAudioData, test_bus->channel(0)[i]);
}
}
audio_device->Stop();
EXPECT_CALL(*ipc, CloseStream());
task_env_.RunUntilIdle();
}
} // namespace media.