blob: 8ccd4a3bb51a8220a53d2b7a6ce859ea0d481393 [file] [log] [blame]
// Copyright 2018 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 "services/audio/public/cpp/output_device.h"
#include <utility>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "media/audio/audio_output_device.h"
#include "media/base/audio_renderer_sink.h"
#include "media/mojo/mojom/audio_data_pipe.mojom.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "services/audio/public/cpp/fake_stream_factory.h"
#include "services/audio/sync_reader.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Invoke;
using testing::Mock;
using testing::NotNull;
using testing::StrictMock;
using testing::WithArg;
namespace audio {
namespace {
constexpr float kAudioData = 0.618;
constexpr base::TimeDelta kDelay = base::Microseconds(123);
constexpr char kDeviceId[] = "testdeviceid";
constexpr int kFramesSkipped = 456;
constexpr int kFrames = 789;
constexpr char kNonDefaultDeviceId[] = "valid-nondefault-device-id";
constexpr base::TimeDelta kAuthTimeout = base::Milliseconds(10000);
constexpr int kBitstreamFrames = 101;
constexpr size_t kBitstreamDataSize = 512;
class MockRenderCallback : public media::AudioRendererSink::RenderCallback {
public:
MockRenderCallback() = default;
MockRenderCallback(const MockRenderCallback&) = delete;
MockRenderCallback& operator=(const MockRenderCallback&) = delete;
~MockRenderCallback() override = default;
MOCK_METHOD4(Render,
int(base::TimeDelta delay,
base::TimeTicks timestamp,
int prior_frames_skipped,
media::AudioBus* dest));
void OnRenderError() {}
};
class MockStream : public media::mojom::AudioOutputStream {
public:
MockStream() = default;
MockStream(const MockStream&) = delete;
MockStream& operator=(const MockStream&) = delete;
~MockStream() override = default;
MOCK_METHOD0(Play, void());
MOCK_METHOD0(Pause, void());
MOCK_METHOD1(SetVolume, void(double));
MOCK_METHOD0(Flush, void());
};
class MockAudioOutputIPC : public media::AudioOutputIPC {
public:
MockAudioOutputIPC() = default;
~MockAudioOutputIPC() override = default;
MOCK_METHOD3(RequestDeviceAuthorization,
void(media::AudioOutputIPCDelegate* delegate,
const base::UnguessableToken& session_id,
const std::string& device_id));
MOCK_METHOD3(
CreateStream,
void(media::AudioOutputIPCDelegate* delegate,
const media::AudioParameters& params,
const absl::optional<base::UnguessableToken>& processing_id));
MOCK_METHOD0(PlayStream, void());
MOCK_METHOD0(PauseStream, void());
MOCK_METHOD0(FlushStream, void());
MOCK_METHOD0(CloseStream, void());
MOCK_METHOD1(SetVolume, void(double volume));
};
class FakeOutputStreamFactory final : public audio::FakeStreamFactory {
public:
FakeOutputStreamFactory() : stream_(), stream_receiver_(&stream_) {}
FakeOutputStreamFactory(const FakeOutputStreamFactory&) = delete;
FakeOutputStreamFactory& operator=(const FakeOutputStreamFactory&) = delete;
~FakeOutputStreamFactory() override {}
void CreateOutputStream(
mojo::PendingReceiver<media::mojom::AudioOutputStream> stream_receiver,
mojo::PendingAssociatedRemote<media::mojom::AudioOutputStreamObserver>
observer,
mojo::PendingRemote<media::mojom::AudioLog> log,
const std::string& output_device_id,
const media::AudioParameters& params,
const base::UnguessableToken& group_id,
CreateOutputStreamCallback created_callback) final {
EXPECT_FALSE(observer);
EXPECT_FALSE(log);
created_callback_ = std::move(created_callback);
if (stream_receiver_.is_bound())
stream_receiver_.reset();
stream_receiver_.Bind(std::move(stream_receiver));
}
void Bind(mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(mojo::PendingReceiver<media::mojom::AudioStreamFactory>(
std::move(handle)));
}
StrictMock<MockStream> stream_;
CreateOutputStreamCallback created_callback_;
private:
mojo::Receiver<media::mojom::AudioOutputStream> stream_receiver_;
};
struct DataFlowTestEnvironment {
explicit DataFlowTestEnvironment(const media::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());
reader = std::make_unique<audio::SyncReader>(
/*log callback*/ base::DoNothing(), params, &client_socket);
CHECK(reader->IsValid());
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::Milliseconds(250));
#endif
}
base::CancelableSyncSocket client_socket;
StrictMock<MockRenderCallback> render_callback;
std::unique_ptr<audio::SyncReader> reader;
base::TimeTicks time_stamp;
};
} // namespace
class AudioServiceOutputDeviceTest : public testing::Test {
public:
AudioServiceOutputDeviceTest()
: task_env_(
base::test::TaskEnvironment::MainThreadType::DEFAULT,
base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED) {
stream_factory_ = std::make_unique<FakeOutputStreamFactory>();
}
AudioServiceOutputDeviceTest(const AudioServiceOutputDeviceTest&) = delete;
AudioServiceOutputDeviceTest& operator=(const AudioServiceOutputDeviceTest&) =
delete;
~AudioServiceOutputDeviceTest() override {
if (!stream_factory_->created_callback_)
return;
std::move(stream_factory_->created_callback_).Run(nullptr);
task_env_.RunUntilIdle();
}
mojo::PendingRemote<media::mojom::AudioStreamFactory> MakeFactoryRemote() {
return stream_factory_->receiver_.BindNewPipeAndPassRemote();
}
base::test::TaskEnvironment task_env_;
std::unique_ptr<FakeOutputStreamFactory> stream_factory_;
};
TEST_F(AudioServiceOutputDeviceTest, CreatePlayPause) {
auto params(media::AudioParameters::UnavailableDeviceParams());
OutputDevice output_device(MakeFactoryRemote(), params, nullptr, kDeviceId);
constexpr double volume = 0.42;
EXPECT_CALL(stream_factory_->stream_, SetVolume(volume));
EXPECT_CALL(stream_factory_->stream_, Play());
EXPECT_CALL(stream_factory_->stream_, Pause());
output_device.SetVolume(volume);
output_device.Play();
output_device.Pause();
task_env_.RunUntilIdle();
}
// Flaky on Linux Chromium OS ASan LSan (https://crbug.com/889845)
#if BUILDFLAG(IS_CHROMEOS_ASH) && defined(ADDRESS_SANITIZER)
#define MAYBE_VerifyDataFlow DISABLED_VerifyDataFlow
#else
#define MAYBE_VerifyDataFlow VerifyDataFlow
#endif
TEST_F(AudioServiceOutputDeviceTest, MAYBE_VerifyDataFlow) {
auto params(media::AudioParameters::UnavailableDeviceParams());
params.set_frames_per_buffer(kFrames);
ASSERT_EQ(2, params.channels());
DataFlowTestEnvironment env(params);
OutputDevice output_device(MakeFactoryRemote(), params, &env.render_callback,
kDeviceId);
EXPECT_CALL(stream_factory_->stream_, Play());
output_device.Play();
task_env_.RunUntilIdle();
std::move(stream_factory_->created_callback_)
.Run({base::in_place, env.reader->TakeSharedMemoryRegion(),
mojo::PlatformHandle(env.client_socket.Take())});
task_env_.RunUntilIdle();
// At this point, the callback thread should be running. Send some data over
// and verify that it's propagated to |env.render_callback|. Do it a few
// times.
auto test_bus = media::AudioBus::Create(params);
for (int i = 0; i < 10; ++i) {
test_bus->Zero();
EXPECT_CALL(env.render_callback,
Render(kDelay, env.time_stamp, kFramesSkipped, NotNull()))
.WillOnce(WithArg<3>(Invoke([](media::AudioBus* client_bus) -> int {
// Place some test data in the bus so that we can check that it was
// copied to the audio service side.
std::fill_n(client_bus->channel(0), client_bus->frames(), kAudioData);
std::fill_n(client_bus->channel(1), client_bus->frames(), kAudioData);
return client_bus->frames();
})));
env.reader->RequestMoreData(kDelay, env.time_stamp, kFramesSkipped);
env.reader->Read(test_bus.get());
Mock::VerifyAndClear(&env.render_callback);
for (int frame = 0; frame < kFrames; ++frame) {
EXPECT_EQ(kAudioData, test_bus->channel(0)[frame]);
EXPECT_EQ(kAudioData, test_bus->channel(1)[frame]);
}
}
}
TEST_F(AudioServiceOutputDeviceTest, CreateBitStreamStream) {
const int kAudioParameterFrames = 4321;
media::AudioParameters params(media::AudioParameters::AUDIO_BITSTREAM_EAC3,
media::CHANNEL_LAYOUT_STEREO, 48000,
kAudioParameterFrames);
DataFlowTestEnvironment env(params);
auto* ipc = new MockAudioOutputIPC(); // owned by |audio_device|.
auto audio_device = base::MakeRefCounted<media::AudioOutputDevice>(
base::WrapUnique(ipc), task_env_.GetMainThreadTaskRunner(),
media::AudioSinkParameters(base::UnguessableToken(), kNonDefaultDeviceId),
kAuthTimeout);
// Start a stream.
audio_device->RequestDeviceAuthorization();
audio_device->Initialize(params, &env.render_callback);
audio_device->Start();
EXPECT_CALL(*ipc, RequestDeviceAuthorization(audio_device.get(),
base::UnguessableToken(),
kNonDefaultDeviceId));
EXPECT_CALL(*ipc, CreateStream(audio_device.get(), _, _));
EXPECT_CALL(*ipc, PlayStream());
task_env_.RunUntilIdle();
Mock::VerifyAndClear(ipc);
audio_device->OnDeviceAuthorized(media::OUTPUT_DEVICE_STATUS_OK, params,
kNonDefaultDeviceId);
audio_device->OnStreamCreated(env.reader->TakeSharedMemoryRegion(),
env.client_socket.Take(),
/*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 = media::AudioBus::Create(params);
for (int i = 0; i < 10; ++i) {
test_bus->Zero();
EXPECT_CALL(env.render_callback,
Render(kDelay, env.time_stamp, kFramesSkipped, NotNull()))
.WillOnce(WithArg<3>(Invoke([](media::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.render_callback);
EXPECT_TRUE(test_bus->is_bitstream_format());
EXPECT_EQ(kBitstreamFrames, test_bus->GetBitstreamFrames());
EXPECT_EQ(kBitstreamDataSize, test_bus->GetBitstreamDataSize());
for (size_t datum = 0; datum < kBitstreamDataSize / sizeof(float);
++datum) {
// 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)[datum]);
}
}
audio_device->Stop();
EXPECT_CALL(*ipc, CloseStream());
task_env_.RunUntilIdle();
}
TEST_F(AudioServiceOutputDeviceTest, CreateNondefaultDevice) {
auto params = media::AudioParameters::UnavailableDeviceParams();
params.set_frames_per_buffer(kFrames);
ASSERT_EQ(2, params.channels());
DataFlowTestEnvironment env(params);
auto* ipc = new MockAudioOutputIPC(); // owned by |audio_device|.
auto audio_device = base::MakeRefCounted<media::AudioOutputDevice>(
base::WrapUnique(ipc), task_env_.GetMainThreadTaskRunner(),
media::AudioSinkParameters(base::UnguessableToken(), kNonDefaultDeviceId),
kAuthTimeout);
audio_device->RequestDeviceAuthorization();
audio_device->Initialize(params, &env.render_callback);
audio_device->Start();
EXPECT_CALL(*ipc, RequestDeviceAuthorization(audio_device.get(),
base::UnguessableToken(),
kNonDefaultDeviceId));
EXPECT_CALL(*ipc, CreateStream(audio_device.get(), _, _));
EXPECT_CALL(*ipc, PlayStream());
task_env_.RunUntilIdle();
Mock::VerifyAndClear(ipc);
audio_device->OnDeviceAuthorized(media::OUTPUT_DEVICE_STATUS_OK, params,
kNonDefaultDeviceId);
audio_device->OnStreamCreated(env.reader->TakeSharedMemoryRegion(),
env.client_socket.Take(),
/*playing_automatically*/ false);
audio_device->Stop();
EXPECT_CALL(*ipc, CloseStream());
task_env_.RunUntilIdle();
}
} // namespace audio