#include "services/audio/public/cpp/output_device.h"
#include <utility>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/audio/audio_output_device.h"
#include "media/base/audio_bus.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::Mock;
using testing::NotNull;
using testing::StrictMock;
using testing::WithArg;
namespace audio {
namespace {
constexpr uint8_t kAudioByteData = 127;
constexpr float kAudioData = 0.618;
constexpr base::TimeDelta kDelay = base::Microseconds(123);
constexpr char kDeviceId[] = "testdeviceid";
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,
const media::AudioGlitchInfo& glitch_info,
media::AudioBus* dest));
void OnRenderError() override {}
};
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_METHOD2(CreateStream,
void(media::AudioOutputIPCDelegate* delegate,
const media::AudioParameters& params));
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>(
base::DoNothing(), params, &client_socket);
CHECK(reader->IsValid());
time_stamp = base::TimeTicks::Now();
#if BUILDFLAG(IS_FUCHSIA)
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;
};
}
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();
}
#if BUILDFLAG(IS_CHROMEOS) && defined(ADDRESS_SANITIZER) || BUILDFLAG(IS_ANDROID)
#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({std::in_place, env.reader->TakeSharedMemoryRegion(),
mojo::PlatformHandle(env.client_socket.Take())});
task_env_.RunUntilIdle();
auto test_bus = media::AudioBus::Create(params);
for (int i = 0; i < 10; ++i) {
test_bus->Zero();
media::AudioGlitchInfo glitch_info{.duration = base::Milliseconds(100),
.count = 123};
EXPECT_CALL(env.render_callback,
Render(kDelay, env.time_stamp, glitch_info, NotNull()))
.WillOnce(WithArg<3>([](media::AudioBus* client_bus) -> int {
std::ranges::fill(client_bus->channel_span(0), kAudioData);
std::ranges::fill(client_bus->channel_span(1), kAudioData);
return client_bus->frames();
}));
env.reader->RequestMoreData(kDelay, env.time_stamp, glitch_info);
env.reader->Read(test_bus.get(), false);
Mock::VerifyAndClear(&env.render_callback);
constexpr auto samples_match = [](float sample) {
return sample == kAudioData;
};
EXPECT_TRUE(std::ranges::all_of(test_bus->channel_span(0), samples_match));
EXPECT_TRUE(std::ranges::all_of(test_bus->channel_span(1), samples_match));
}
}
TEST_F(AudioServiceOutputDeviceTest, CreateBitStreamStream) {
const int kAudioParameterFrames = 4321;
media::AudioParameters params(media::AudioParameters::AUDIO_BITSTREAM_EAC3,
media::ChannelLayoutConfig::Stereo(), 48000,
kAudioParameterFrames);
DataFlowTestEnvironment env(params);
auto* ipc = new MockAudioOutputIPC();
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(),
false);
task_env_.RunUntilIdle();
auto test_bus = media::AudioBus::Create(params);
for (int i = 0; i < 10; ++i) {
test_bus->Zero();
media::AudioGlitchInfo glitch_info{.duration = base::Milliseconds(100),
.count = 123};
EXPECT_CALL(env.render_callback,
Render(kDelay, env.time_stamp, glitch_info, NotNull()))
.WillOnce(WithArg<3>([](media::AudioBus* renderer_bus) -> int {
EXPECT_TRUE(renderer_bus->is_bitstream_format());
renderer_bus->SetBitstreamFrames(kBitstreamFrames);
renderer_bus->SetBitstreamSize(kBitstreamDataSize);
std::ranges::fill(renderer_bus->bitstream_data(), kAudioByteData);
return renderer_bus->frames();
}));
env.reader->RequestMoreData(kDelay, env.time_stamp, glitch_info);
env.reader->Read(test_bus.get(), false);
Mock::VerifyAndClear(&env.render_callback);
EXPECT_TRUE(test_bus->is_bitstream_format());
EXPECT_EQ(kBitstreamFrames, test_bus->GetBitstreamFrames());
EXPECT_EQ(kBitstreamDataSize, test_bus->bitstream_data().size());
for (auto datum : test_bus->bitstream_data()) {
ASSERT_EQ(kAudioByteData, 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();
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(),
false);
audio_device->Stop();
EXPECT_CALL(*ipc, CloseStream());
task_env_.RunUntilIdle();
}
}