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

Side by Side Diff: content/renderer/pepper/pepper_platform_audio_output_dev.cc

Issue 2755613002: Support audio output device enumeration and selection in PPAPI (Closed)
Patch Set: Fix issues from previous review, Rebase. Created 3 years, 8 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 (c) 2017 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/renderer/pepper/pepper_platform_audio_output_dev.h"
6
7 #include "base/bind.h"
8 #include "base/location.h"
9 #include "base/logging.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/threading/thread_task_runner_handle.h"
12 #include "base/time/time.h"
13 #include "base/timer/timer.h"
14 #include "build/build_config.h"
15 #include "content/child/child_process.h"
16 #include "content/common/content_constants_internal.h"
17 #include "content/common/media/audio_messages.h"
18 #include "content/renderer/media/audio_message_filter.h"
19 #include "content/renderer/pepper/audio_helper.h"
20 #include "content/renderer/pepper/pepper_audio_output_host.h"
21 #include "content/renderer/pepper/pepper_media_device_manager.h"
22 #include "content/renderer/render_frame_impl.h"
23 #include "content/renderer/render_thread_impl.h"
24 #include "media/audio/audio_device_description.h"
25 #include "ppapi/shared_impl/ppb_audio_config_shared.h"
26
27 namespace content {
28
29 // static
30 PepperPlatformAudioOutputDev* PepperPlatformAudioOutputDev::Create(
31 int render_frame_id,
32 const std::string& device_id,
33 const GURL& document_url,
34 int sample_rate,
35 int frames_per_buffer,
36 PepperAudioOutputHost* client) {
37 scoped_refptr<PepperPlatformAudioOutputDev> audio_output(
38 new PepperPlatformAudioOutputDev(
39 render_frame_id, device_id, document_url,
40 // Set authorization request timeout at 80% of renderer hung timeout,
41 // but no more than kMaxAuthorizationTimeout.
42 base::TimeDelta::FromMilliseconds(std::min(
43 kHungRendererDelayMs * 8 / 10, kMaxAuthorizationTimeoutMs))));
44
45 if (audio_output->Initialize(sample_rate, frames_per_buffer, client)) {
46 // Balanced by Release invoked in
47 // PepperPlatformAudioOutputDev::ShutDownOnIOThread().
48 audio_output->AddRef();
49 return audio_output.get();
50 }
51 return NULL;
52 }
53
54 void PepperPlatformAudioOutputDev::RequestDeviceAuthorization() {
55 if (ipc_) {
56 io_task_runner_->PostTask(
57 FROM_HERE,
58 base::Bind(
59 &PepperPlatformAudioOutputDev::RequestDeviceAuthorizationOnIOThread,
60 this));
61 }
62 }
63
64 bool PepperPlatformAudioOutputDev::StartPlayback() {
65 if (ipc_) {
66 io_task_runner_->PostTask(
67 FROM_HERE,
68 base::Bind(&PepperPlatformAudioOutputDev::StartPlaybackOnIOThread,
69 this));
70 return true;
71 }
72 return false;
73 }
74
75 bool PepperPlatformAudioOutputDev::StopPlayback() {
76 if (ipc_) {
77 io_task_runner_->PostTask(
78 FROM_HERE,
79 base::Bind(&PepperPlatformAudioOutputDev::StopPlaybackOnIOThread,
80 this));
81 return true;
82 }
83 return false;
84 }
85
86 bool PepperPlatformAudioOutputDev::SetVolume(double volume) {
87 if (ipc_) {
88 io_task_runner_->PostTask(
89 FROM_HERE,
90 base::Bind(&PepperPlatformAudioOutputDev::SetVolumeOnIOThread, this,
91 volume));
92 return true;
93 }
94 return false;
95 }
96
97 void PepperPlatformAudioOutputDev::ShutDown() {
98 // Called on the main thread to stop all audio callbacks. We must only change
99 // the client on the main thread, and the delegates from the I/O thread.
100 client_ = NULL;
101 io_task_runner_->PostTask(
102 FROM_HERE,
103 base::Bind(&PepperPlatformAudioOutputDev::ShutDownOnIOThread, this));
104 }
105
106 void PepperPlatformAudioOutputDev::OnError() {
107 DCHECK(io_task_runner_->BelongsToCurrentThread());
108
109 // Do nothing if the stream has been closed.
110 if (state_ < CREATING_STREAM)
111 return;
112
113 DLOG(WARNING) << "PepperPlatformAudioOutputDev::OnError()";
114 }
115
116 void PepperPlatformAudioOutputDev::OnDeviceAuthorized(
117 media::OutputDeviceStatus device_status,
118 const media::AudioParameters& output_params,
119 const std::string& matched_device_id) {
120 DCHECK(io_task_runner_->BelongsToCurrentThread());
121
122 auth_timeout_action_.reset();
123
124 // Do nothing if late authorization is received after timeout.
125 if (state_ == IPC_CLOSED)
126 return;
127
128 LOG_IF(WARNING, device_status == media::OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT)
129 << "Output device authorization timed out";
130
131 DCHECK_EQ(state_, AUTHORIZING);
132
133 // It may happen that a second authorization is received as a result to a
134 // call to StartPlayback() after Shutdown(). If the status for the second
135 // authorization differs from the first, it will not be reflected in
136 // |device_status_| to avoid a race.
137 // This scenario is unlikely. If it occurs, the new value will be
138 // different from OUTPUT_DEVICE_STATUS_OK, so the PepperPlatformAudioOutputDev
139 // will enter the IPC_CLOSED state anyway, which is the safe thing to do.
140 // This is preferable to holding a lock.
141 if (!did_receive_auth_.IsSignaled())
142 device_status_ = device_status;
143
144 if (device_status == media::OUTPUT_DEVICE_STATUS_OK) {
145 state_ = AUTHORIZED;
146 if (!did_receive_auth_.IsSignaled()) {
147 output_params_ = output_params;
148
149 // It's possible to not have a matched device obtained via session id. It
150 // means matching output device through |session_id_| failed and the
151 // default device is used.
152 DCHECK(media::AudioDeviceDescription::UseSessionIdToSelectDevice(
153 session_id_, device_id_) ||
154 matched_device_id_.empty());
155 matched_device_id_ = matched_device_id;
156
157 DVLOG(1) << "PepperPlatformAudioOutputDev authorized, session_id: "
158 << session_id_ << ", device_id: " << device_id_
159 << ", matched_device_id: " << matched_device_id_;
160
161 did_receive_auth_.Signal();
162 }
163 if (start_on_authorized_)
164 CreateStreamOnIOThread(params_);
165 } else {
166 // Closing IPC forces a Signal(), so no clients are locked waiting
167 // indefinitely after this method returns.
168 ipc_->CloseStream();
169 OnIPCClosed();
170 main_task_runner_->PostTask(
171 FROM_HERE,
172 base::Bind(&PepperPlatformAudioOutputDev::NotifyStreamCreationFailed,
173 this));
174 }
175 }
176
177 void PepperPlatformAudioOutputDev::OnStreamCreated(
178 base::SharedMemoryHandle handle,
179 base::SyncSocket::Handle socket_handle,
180 int length) {
181 #if defined(OS_WIN)
182 DCHECK(handle.IsValid());
183 DCHECK(socket_handle);
184 #else
185 DCHECK(base::SharedMemory::IsHandleValid(handle));
186 DCHECK_NE(-1, socket_handle);
187 #endif
188 DCHECK(length);
189
190 if (base::ThreadTaskRunnerHandle::Get().get() == main_task_runner_.get()) {
191 // Must dereference the client only on the main thread. Shutdown may have
192 // occurred while the request was in-flight, so we need to NULL check.
193 if (client_)
194 client_->StreamCreated(handle, length, socket_handle);
195 } else {
196 DCHECK(io_task_runner_->BelongsToCurrentThread());
197 if (state_ != CREATING_STREAM)
198 return;
199
200 state_ = PAUSED;
201 if (play_on_start_)
202 StartPlaybackOnIOThread();
203
204 main_task_runner_->PostTask(
205 FROM_HERE, base::Bind(&PepperPlatformAudioOutputDev::OnStreamCreated,
206 this, handle, socket_handle, length));
207 }
208 }
209
210 void PepperPlatformAudioOutputDev::OnIPCClosed() {
211 DCHECK(io_task_runner_->BelongsToCurrentThread());
212 state_ = IPC_CLOSED;
213 ipc_.reset();
214
215 // Signal to unblock any blocked threads waiting for parameters
216 did_receive_auth_.Signal();
217 }
218
219 PepperPlatformAudioOutputDev::~PepperPlatformAudioOutputDev() {
220 // Make sure we have been shut down. Warning: this will usually happen on
221 // the I/O thread!
222 DCHECK(!ipc_);
223 DCHECK(!client_);
224 }
225
226 PepperPlatformAudioOutputDev::PepperPlatformAudioOutputDev(
227 int render_frame_id,
228 const std::string& device_id,
229 const GURL& document_url,
230 base::TimeDelta authorization_timeout)
231 : client_(NULL),
232 main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
233 io_task_runner_(ChildProcess::current()->io_task_runner()),
234 render_frame_id_(render_frame_id),
235 state_(IDLE),
236 start_on_authorized_(true),
237 play_on_start_(false),
238 session_id_(0),
239 device_id_(device_id),
240 security_origin_(document_url),
241 did_receive_auth_(base::WaitableEvent::ResetPolicy::MANUAL,
242 base::WaitableEvent::InitialState::NOT_SIGNALED),
243 device_status_(media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL),
244 auth_timeout_(authorization_timeout) {}
245
246 bool PepperPlatformAudioOutputDev::Initialize(int sample_rate,
247 int frames_per_buffer,
248 PepperAudioOutputHost* client) {
249 DCHECK(main_task_runner_->BelongsToCurrentThread());
250
251 RenderFrameImpl* const render_frame =
252 RenderFrameImpl::FromRoutingID(render_frame_id_);
253 if (!render_frame || !client)
254 return false;
255
256 client_ = client;
257
258 RenderThreadImpl* const render_thread = RenderThreadImpl::current();
259 ipc_ = render_thread->audio_message_filter()->CreateAudioOutputIPC(
260 render_frame_id_);
261 CHECK(ipc_);
262
263 params_.Reset(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
264 media::CHANNEL_LAYOUT_STEREO, sample_rate,
265 ppapi::kBitsPerAudioOutputSample, frames_per_buffer);
266
267 io_task_runner_->PostTask(
268 FROM_HERE,
269 base::Bind(&PepperPlatformAudioOutputDev::CreateStreamOnIOThread, this,
270 params_));
271
272 return true;
273 }
274
275 void PepperPlatformAudioOutputDev::RequestDeviceAuthorizationOnIOThread() {
276 DCHECK(io_task_runner_->BelongsToCurrentThread());
277 DCHECK_EQ(state_, IDLE);
278
279 if (!ipc_)
280 return;
281
282 state_ = AUTHORIZING;
283 ipc_->RequestDeviceAuthorization(this, session_id_, device_id_,
284 security_origin_);
285
286 if (auth_timeout_ > base::TimeDelta()) {
287 // Create the timer on the thread it's used on. It's guaranteed to be
288 // deleted on the same thread since users must call ShutDown() before
289 // deleting PepperPlatformAudioOutputDev; see ShutDownOnIOThread().
290 auth_timeout_action_.reset(new base::OneShotTimer());
291 auth_timeout_action_->Start(
292 FROM_HERE, auth_timeout_,
293 base::Bind(&PepperPlatformAudioOutputDev::OnDeviceAuthorized, this,
294 media::OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT,
295 media::AudioParameters(), std::string()));
296 }
297 }
298
299 void PepperPlatformAudioOutputDev::CreateStreamOnIOThread(
300 const media::AudioParameters& params) {
301 DCHECK(io_task_runner_->BelongsToCurrentThread());
302 switch (state_) {
303 case IPC_CLOSED:
304 main_task_runner_->PostTask(
305 FROM_HERE,
306 base::Bind(&PepperPlatformAudioOutputDev::NotifyStreamCreationFailed,
307 this));
308 break;
309
310 case IDLE:
311 if (did_receive_auth_.IsSignaled() && device_id_.empty() &&
312 security_origin_.unique()) {
313 state_ = CREATING_STREAM;
314 ipc_->CreateStream(this, params);
315 } else {
316 RequestDeviceAuthorizationOnIOThread();
317 start_on_authorized_ = true;
318 }
319 break;
320
321 case AUTHORIZING:
322 start_on_authorized_ = true;
323 break;
324
325 case AUTHORIZED:
326 state_ = CREATING_STREAM;
327 ipc_->CreateStream(this, params);
328 start_on_authorized_ = false;
329 break;
330
331 case CREATING_STREAM:
332 case PAUSED:
333 case PLAYING:
334 NOTREACHED();
335 break;
336 }
337 }
338
339 void PepperPlatformAudioOutputDev::StartPlaybackOnIOThread() {
340 DCHECK(io_task_runner_->BelongsToCurrentThread());
341 if (!ipc_)
342 return;
343
344 if (state_ == PAUSED) {
345 ipc_->PlayStream();
346 state_ = PLAYING;
347 play_on_start_ = false;
348 } else {
349 if (state_ < CREATING_STREAM)
350 CreateStreamOnIOThread(params_);
351
352 play_on_start_ = true;
353 }
354 }
355
356 void PepperPlatformAudioOutputDev::StopPlaybackOnIOThread() {
357 DCHECK(io_task_runner_->BelongsToCurrentThread());
358 if (!ipc_)
359 return;
360
361 if (state_ == PLAYING) {
362 ipc_->PauseStream();
363 state_ = PAUSED;
364 }
365 play_on_start_ = false;
366 }
367
368 void PepperPlatformAudioOutputDev::SetVolumeOnIOThread(double volume) {
369 DCHECK(io_task_runner_->BelongsToCurrentThread());
370 if (!ipc_)
371 return;
372
373 if (state_ >= CREATING_STREAM)
374 ipc_->SetVolume(volume);
375 }
376
377 void PepperPlatformAudioOutputDev::ShutDownOnIOThread() {
378 DCHECK(io_task_runner_->BelongsToCurrentThread());
379
380 // Make sure we don't call shutdown more than once.
381 if (!ipc_)
382 return;
383
384 // Close the stream, if we haven't already.
385 if (state_ >= AUTHORIZING) {
386 ipc_->CloseStream();
387 ipc_.reset();
388 state_ = IDLE;
389 }
390 start_on_authorized_ = false;
391
392 // Destoy the timer on the thread it's used on.
393 auth_timeout_action_.reset();
394
395 // Release for the delegate, balances out the reference taken in
396 // PepperPlatformAudioOutputDev::Create.
397 Release();
398 }
399
400 void PepperPlatformAudioOutputDev::NotifyStreamCreationFailed() {
401 DCHECK(main_task_runner_->BelongsToCurrentThread());
402
403 if (client_)
404 client_->StreamCreationFailed();
405 }
406
407 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698