OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "content/browser/renderer_host/media/audio_output_service_context_impl.
h" |
| 6 |
| 7 #include <cmath> |
| 8 #include <utility> |
| 9 |
| 10 #include "base/bind.h" |
| 11 #include "base/memory/shared_memory.h" |
| 12 #include "base/memory/shared_memory_handle.h" |
| 13 #include "base/run_loop.h" |
| 14 #include "base/sync_socket.h" |
| 15 #include "cc/base/math_util.h" |
| 16 #include "content/browser/audio_manager_thread.h" |
| 17 #include "content/browser/renderer_host/media/media_stream_manager.h" |
| 18 #include "content/common/media/audio_output.mojom.h" |
| 19 #include "content/public/browser/browser_thread.h" |
| 20 #include "content/public/test/mock_render_process_host.h" |
| 21 #include "content/public/test/test_browser_context.h" |
| 22 #include "content/public/test/test_browser_thread_bundle.h" |
| 23 #include "media/audio/audio_manager_base.h" |
| 24 #include "media/audio/audio_output_controller.h" |
| 25 #include "media/audio/fake_audio_log_factory.h" |
| 26 #include "media/base/audio_parameters.h" |
| 27 #include "media/base/media_switches.h" |
| 28 #include "mojo/public/cpp/bindings/binding.h" |
| 29 #include "mojo/public/cpp/system/platform_handle.h" |
| 30 #include "testing/gmock/include/gmock/gmock.h" |
| 31 #include "testing/gtest/include/gtest/gtest.h" |
| 32 |
| 33 namespace content { |
| 34 |
| 35 namespace { |
| 36 |
| 37 using testing::_; |
| 38 using testing::StrictMock; |
| 39 using testing::Return; |
| 40 using testing::Test; |
| 41 using Signal = std::vector<std::unique_ptr<const media::AudioBus>>; |
| 42 using AudioOutputService = mojom::RendererAudioOutputService; |
| 43 using AudioOutputServicePtr = mojo::InterfacePtr<AudioOutputService>; |
| 44 using AudioOutputServiceRequest = mojo::InterfaceRequest<AudioOutputService>; |
| 45 using AudioOutput = mojom::AudioOutput; |
| 46 using AudioOutputPtr = mojo::InterfacePtr<AudioOutput>; |
| 47 using AudioOutputRequest = mojo::InterfaceRequest<AudioOutput>; |
| 48 |
| 49 const int kRenderProcessId = 42; |
| 50 const int kRenderFrameId = 24; |
| 51 const char kSecurityOrigin[] = "http://localhost"; |
| 52 const char kSalt[] = "salt"; |
| 53 const double pi = std::acos(-1); |
| 54 const media::AudioParameters params( |
| 55 media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| 56 media::CHANNEL_LAYOUT_MONO, |
| 57 44100 /*sample frequency*/, |
| 58 16 /*bits per sample*/, |
| 59 441 /*10 ms buffers*/); |
| 60 |
| 61 Signal MakeSineWave() { |
| 62 Signal s; |
| 63 s.reserve(1000); |
| 64 int64_t t = 0; |
| 65 for (int i = 0; i < 1000; i++) { |
| 66 std::unique_ptr<media::AudioBus> bus = media::AudioBus::Create(params); |
| 67 float* channel = bus->channel(0); |
| 68 int frames = params.frames_per_buffer(); |
| 69 for (int j = 0; j < frames; ++j) { |
| 70 ++t; |
| 71 channel[j] = std::sin(t * params.GetMicrosecondsPerFrame() / 1000000.0 * |
| 72 440 * 2 * pi); |
| 73 } |
| 74 s.push_back(std::move(bus)); |
| 75 } |
| 76 return s; |
| 77 } |
| 78 |
| 79 void SyncWith(scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
| 80 CHECK(task_runner); |
| 81 CHECK(!task_runner->BelongsToCurrentThread()); |
| 82 base::WaitableEvent e = {base::WaitableEvent::ResetPolicy::MANUAL, |
| 83 base::WaitableEvent::InitialState::NOT_SIGNALED}; |
| 84 task_runner->PostTask(FROM_HERE, base::Bind(&base::WaitableEvent::Signal, |
| 85 base::Unretained(&e))); |
| 86 e.Wait(); |
| 87 } |
| 88 |
| 89 void SyncWithAllThreads() { |
| 90 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 91 // New tasks might be posted while we are syncing, but in every iteration at |
| 92 // least one task will be run. 20 iterations should be enough for our code. |
| 93 for (int i = 0; i < 20; ++i) { |
| 94 { |
| 95 base::MessageLoop::ScopedNestableTaskAllower allower( |
| 96 base::MessageLoop::current()); |
| 97 base::RunLoop().RunUntilIdle(); |
| 98 } |
| 99 SyncWith(BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)); |
| 100 SyncWith(media::AudioManager::Get()->GetWorkerTaskRunner()); |
| 101 } |
| 102 } |
| 103 |
| 104 class MockAudioManager : public media::AudioManagerBase { |
| 105 public: |
| 106 MockAudioManager( |
| 107 scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| 108 scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner, |
| 109 media::AudioLogFactory* audio_log_factory) |
| 110 : media::AudioManagerBase(task_runner, |
| 111 worker_task_runner, |
| 112 audio_log_factory) {} |
| 113 |
| 114 ~MockAudioManager() override { Shutdown(); } |
| 115 |
| 116 MOCK_METHOD0(HasAudioOutputDevices, bool()); |
| 117 MOCK_METHOD0(HasAudioInputDevices, bool()); |
| 118 MOCK_METHOD0(GetName, const char*()); |
| 119 |
| 120 MOCK_METHOD2(MakeLinearOutputStream, |
| 121 media::AudioOutputStream*(const media::AudioParameters& params, |
| 122 const LogCallback& log_callback)); |
| 123 MOCK_METHOD3(MakeLowLatencyOutputStream, |
| 124 media::AudioOutputStream*(const media::AudioParameters& params, |
| 125 const std::string& device_id, |
| 126 const LogCallback& log_callback)); |
| 127 MOCK_METHOD3(MakeLinearInputStream, |
| 128 media::AudioInputStream*(const media::AudioParameters& params, |
| 129 const std::string& device_id, |
| 130 const LogCallback& log_callback)); |
| 131 MOCK_METHOD3(MakeLowLatencyInputStream, |
| 132 media::AudioInputStream*(const media::AudioParameters& params, |
| 133 const std::string& device_id, |
| 134 const LogCallback& log_callback)); |
| 135 MOCK_METHOD2(GetPreferredOutputStreamParameters, |
| 136 media::AudioParameters(const std::string& device_id, |
| 137 const media::AudioParameters& params)); |
| 138 }; |
| 139 |
| 140 class MockAudioOutputStream : public media::AudioOutputStream, |
| 141 public base::PlatformThread::Delegate { |
| 142 public: |
| 143 explicit MockAudioOutputStream(MockAudioManager* audio_manager) |
| 144 : audio_manager_(audio_manager) {} |
| 145 ~MockAudioOutputStream() override { |
| 146 base::PlatformThread::Join(thread_handle_); |
| 147 } |
| 148 |
| 149 void Start(AudioSourceCallback* callback) override { |
| 150 callback_ = callback; |
| 151 EXPECT_TRUE(base::PlatformThread::CreateWithPriority( |
| 152 0, this, &thread_handle_, base::ThreadPriority::REALTIME_AUDIO)); |
| 153 } |
| 154 |
| 155 void Stop() override {} |
| 156 |
| 157 bool Open() override { return true; } |
| 158 void SetVolume(double volume) override {} |
| 159 void GetVolume(double* volume) override { *volume = 1; } |
| 160 void Close() override { audio_manager_->ReleaseOutputStream(this); } |
| 161 |
| 162 void ThreadMain() override { |
| 163 Signal expected = MakeSineWave(); |
| 164 std::unique_ptr<media::AudioBus> dest = media::AudioBus::Create(params); |
| 165 for (const auto& bus : expected) { |
| 166 callback_->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), 0, |
| 167 dest.get()); |
| 168 for (int frame = 0; frame < params.frames_per_buffer(); ++frame) { |
| 169 // Using EXPECT here causes massive log spam in case of a broken test, |
| 170 // and ASSERT causes it to hang, so we use CHECK. |
| 171 CHECK(cc::MathUtil::IsNearlyTheSameForTesting(bus->channel(0)[frame], |
| 172 dest->channel(0)[frame])) |
| 173 << "Got " << dest->channel(0)[frame] << ", expected " |
| 174 << bus->channel(0)[frame]; |
| 175 } |
| 176 } |
| 177 } |
| 178 |
| 179 private: |
| 180 base::OnceClosure sync_closure_; |
| 181 base::PlatformThreadHandle thread_handle_; |
| 182 MockAudioManager* audio_manager_; |
| 183 AudioSourceCallback* callback_; |
| 184 }; |
| 185 |
| 186 void AuthCallback2(base::OnceClosure sync_closure, |
| 187 media::OutputDeviceStatus* status_out, |
| 188 media::AudioParameters* params_out, |
| 189 std::string* id_out, |
| 190 media::OutputDeviceStatus status, |
| 191 const media::AudioParameters& params, |
| 192 const std::string& id) { |
| 193 *status_out = status; |
| 194 *params_out = params; |
| 195 *id_out = id; |
| 196 std::move(sync_closure).Run(); |
| 197 } |
| 198 |
| 199 // "Renderer-side" audio client. Sends a signal from a dedicated thread. |
| 200 // TODO(maxmorin): Replace with an instance of the real client, when it exists. |
| 201 class TestClient : public base::PlatformThread::Delegate { |
| 202 public: |
| 203 TestClient() {} |
| 204 |
| 205 ~TestClient() override { base::PlatformThread::Join(thread_handle_); } |
| 206 |
| 207 // Starts thread, sets up IPC primitives and sends signal on thread. |
| 208 void Start(Signal s, |
| 209 mojo::ScopedSharedBufferHandle shared_buffer, |
| 210 mojo::ScopedHandle socket_handle) { |
| 211 s_ = std::move(s); |
| 212 |
| 213 EXPECT_TRUE(socket_handle.is_valid()); |
| 214 // Set up socket. |
| 215 base::PlatformFile fd; |
| 216 mojo::UnwrapPlatformFile(std::move(socket_handle), &fd); |
| 217 socket_ = base::MakeUnique<base::CancelableSyncSocket>(fd); |
| 218 EXPECT_NE(socket_->handle(), base::CancelableSyncSocket::kInvalidHandle); |
| 219 |
| 220 // Set up memory. |
| 221 EXPECT_TRUE(shared_buffer.is_valid()); |
| 222 size_t memory_length; |
| 223 base::SharedMemoryHandle shmem_handle; |
| 224 bool read_only; |
| 225 EXPECT_EQ( |
| 226 mojo::UnwrapSharedMemoryHandle(std::move(shared_buffer), &shmem_handle, |
| 227 &memory_length, &read_only), |
| 228 MOJO_RESULT_OK); |
| 229 EXPECT_EQ(memory_length, sizeof(media::AudioOutputBufferParameters) + |
| 230 media::AudioBus::CalculateMemorySize(params)); |
| 231 EXPECT_EQ(read_only, false); |
| 232 memory_ = base::MakeUnique<base::SharedMemory>(shmem_handle, read_only); |
| 233 EXPECT_TRUE(memory_->Map(memory_length)); |
| 234 |
| 235 EXPECT_TRUE(base::PlatformThread::CreateWithPriority( |
| 236 0, this, &thread_handle_, base::ThreadPriority::REALTIME_AUDIO)); |
| 237 } |
| 238 |
| 239 void ThreadMain() override { |
| 240 media::AudioOutputBuffer* buffer = |
| 241 reinterpret_cast<media::AudioOutputBuffer*>(memory_->memory()); |
| 242 std::unique_ptr<media::AudioBus> output_bus = |
| 243 media::AudioBus::WrapMemory(params, buffer->audio); |
| 244 |
| 245 // Send s. |
| 246 uint32_t buffer_index = 0; |
| 247 for (const auto& bus : s_) { |
| 248 uint32_t pending_data = 0; |
| 249 size_t bytes_read = socket_->Receive(&pending_data, sizeof(pending_data)); |
| 250 EXPECT_EQ(sizeof(pending_data), bytes_read); |
| 251 EXPECT_EQ(0u, pending_data); |
| 252 |
| 253 ++buffer_index; |
| 254 |
| 255 bus->CopyTo(output_bus.get()); |
| 256 |
| 257 size_t bytes_written = socket_->Send(&buffer_index, sizeof(buffer_index)); |
| 258 EXPECT_EQ(sizeof(buffer_index), bytes_written); |
| 259 } |
| 260 } |
| 261 |
| 262 private: |
| 263 base::PlatformThreadHandle thread_handle_; |
| 264 Signal s_; |
| 265 std::unique_ptr<base::CancelableSyncSocket> socket_; |
| 266 std::unique_ptr<base::SharedMemory> memory_; |
| 267 }; |
| 268 |
| 269 } // namespace |
| 270 |
| 271 class AudioOutputServiceIntegrationTest : public Test { |
| 272 public: |
| 273 AudioOutputServiceIntegrationTest() |
| 274 : media_stream_manager_(), |
| 275 thread_bundle_(TestBrowserThreadBundle::Options::REAL_IO_THREAD), |
| 276 audio_thread_(), |
| 277 log_factory_(), |
| 278 audio_manager_( |
| 279 new StrictMock<MockAudioManager>(audio_thread_.task_runner(), |
| 280 audio_thread_.worker_task_runner(), |
| 281 &log_factory_)) { |
| 282 media_stream_manager_ = |
| 283 base::MakeUnique<MediaStreamManager>(audio_manager_.get()); |
| 284 } |
| 285 |
| 286 ~AudioOutputServiceIntegrationTest() override { SyncWithAllThreads(); } |
| 287 |
| 288 void CreateAndBindService(AudioOutputServiceRequest request) { |
| 289 service_context_.reset(new AudioOutputServiceContextImpl( |
| 290 kRenderProcessId, audio_manager_.get(), media_stream_manager_.get(), |
| 291 kSalt)); |
| 292 service_context_->CreateService(kRenderFrameId, std::move(request)); |
| 293 } |
| 294 |
| 295 std::unique_ptr<MediaStreamManager> media_stream_manager_; |
| 296 TestBrowserThreadBundle thread_bundle_; |
| 297 AudioManagerThread audio_thread_; |
| 298 media::FakeAudioLogFactory log_factory_; |
| 299 media::ScopedAudioManagerPtr audio_manager_; |
| 300 std::unique_ptr<AudioOutputServiceContextImpl, |
| 301 BrowserThread::DeleteOnIOThread> |
| 302 service_context_; |
| 303 }; |
| 304 |
| 305 TEST_F(AudioOutputServiceIntegrationTest, StreamIntegrationTest) { |
| 306 // Sets up the service on the IO thread and runs client code on the UI thread. |
| 307 // Send a sine wave from the client and makes sure it's received by the output |
| 308 // stream. |
| 309 MockAudioOutputStream* stream = new MockAudioOutputStream( |
| 310 static_cast<MockAudioManager*>(audio_manager_.get())); |
| 311 |
| 312 // Make sure the mock audio manager uses our mock stream. |
| 313 EXPECT_CALL(*static_cast<MockAudioManager*>(audio_manager_.get()), |
| 314 MakeLowLatencyOutputStream(_, "", _)) |
| 315 .WillOnce(Return(stream)); |
| 316 EXPECT_CALL(*static_cast<MockAudioManager*>(audio_manager_.get()), |
| 317 GetPreferredOutputStreamParameters(_, _)) |
| 318 .WillRepeatedly(Return(params)); |
| 319 |
| 320 AudioOutputServicePtr service_ptr; |
| 321 BrowserThread::PostTask( |
| 322 BrowserThread::IO, FROM_HERE, |
| 323 base::Bind(&AudioOutputServiceIntegrationTest::CreateAndBindService, |
| 324 base::Unretained(this), |
| 325 base::Passed(mojo::MakeRequest(&service_ptr)))); |
| 326 |
| 327 AudioOutputPtr output_ptr; |
| 328 base::RunLoop loop; |
| 329 media::OutputDeviceStatus status; |
| 330 media::AudioParameters params2; |
| 331 std::string id; |
| 332 service_ptr->RequestDeviceAuthorization( |
| 333 mojo::MakeRequest<AudioOutput>(&output_ptr), /*session_id*/ 0, "default", |
| 334 url::Origin(GURL(kSecurityOrigin)), |
| 335 base::Bind(&AuthCallback2, base::Passed(loop.QuitWhenIdleClosure()), |
| 336 base::Unretained(&status), base::Unretained(¶ms2), |
| 337 base::Unretained(&id))); |
| 338 loop.Run(); |
| 339 ASSERT_EQ(status, media::OUTPUT_DEVICE_STATUS_OK); |
| 340 ASSERT_EQ(params.AsHumanReadableString(), params2.AsHumanReadableString()); |
| 341 ASSERT_TRUE(id.empty()); |
| 342 |
| 343 { |
| 344 TestClient client; |
| 345 output_ptr->Start(params, |
| 346 base::Bind(&TestClient::Start, base::Unretained(&client), |
| 347 base::Passed(MakeSineWave()))); |
| 348 output_ptr->Play(); |
| 349 SyncWithAllThreads(); |
| 350 } // Joining client thread. |
| 351 output_ptr.reset(); |
| 352 SyncWithAllThreads(); // Joins stream thread. |
| 353 } |
| 354 |
| 355 } // namespace content |
OLD | NEW |