| // 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. |