Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1013)

Side by Side Diff: content/browser/renderer_host/media/audio_output_service_context_impl_unittest.cc

Issue 2319493002: Add mojo interface for audio rendering. (Closed)
Patch Set: ++docs Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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(&params2),
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698