[go: nahoru, domu]

blob: 1198e9707bcb08ec0f8218ee5b37ca88f5bc854a [file] [log] [blame]
Avi Drissman4e1b7bc2022-09-15 14:03:501// Copyright 2017 The Chromium Authors
xzhang3a2a4702017-04-07 16:34:302// 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
Peter Boströmdd7e40ec2021-04-05 20:40:107#include <memory>
8
Avi Drissmanadac21992023-01-11 23:46:399#include "base/functional/bind.h"
xzhang3a2a4702017-04-07 16:34:3010#include "base/location.h"
11#include "base/logging.h"
David Sanders10242c12022-03-17 07:36:4412#include "base/synchronization/waitable_event.h"
Patrick Monette643cdf62021-10-15 19:13:4213#include "base/task/single_thread_task_runner.h"
xzhang3a2a4702017-04-07 16:34:3014#include "base/time/time.h"
15#include "base/timer/timer.h"
16#include "build/build_config.h"
17#include "content/child/child_process.h"
18#include "content/common/content_constants_internal.h"
xzhang3a2a4702017-04-07 16:34:3019#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"
xzhang3a2a4702017-04-07 16:34:3023#include "media/audio/audio_device_description.h"
24#include "ppapi/shared_impl/ppb_audio_config_shared.h"
deejay.kim21b2beb92021-11-30 23:16:2725#include "third_party/blink/public/web/modules/media/audio/audio_output_ipc_factory.h"
Guido Urdaneta6bd37bb2020-07-17 16:43:1526#include "third_party/blink/public/web/web_local_frame.h"
xzhang3a2a4702017-04-07 16:34:3027
maxmorin017ba8c62017-06-02 10:23:0928namespace {
Xiaohan Wang0ac63f832022-01-15 00:06:3629#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
Peter Kastinge5a38ed2021-10-02 03:06:3530constexpr base::TimeDelta kMaxAuthorizationTimeout = base::Seconds(4);
maxmorin017ba8c62017-06-02 10:23:0931#else
Scott Violet93e21e82020-05-22 00:43:5332constexpr base::TimeDelta kMaxAuthorizationTimeout; // No timeout.
maxmorin017ba8c62017-06-02 10:23:0933#endif
34}
35
xzhang3a2a4702017-04-07 16:34:3036namespace content {
37
38// static
39PepperPlatformAudioOutputDev* PepperPlatformAudioOutputDev::Create(
40 int render_frame_id,
41 const std::string& device_id,
xzhang3a2a4702017-04-07 16:34:3042 int sample_rate,
43 int frames_per_buffer,
44 PepperAudioOutputHost* client) {
45 scoped_refptr<PepperPlatformAudioOutputDev> audio_output(
46 new PepperPlatformAudioOutputDev(
Max Morinf506af592018-04-17 12:23:3247 render_frame_id, device_id,
xzhang3a2a4702017-04-07 16:34:3048 // Set authorization request timeout at 80% of renderer hung timeout,
49 // but no more than kMaxAuthorizationTimeout.
Scott Violet93e21e82020-05-22 00:43:5350 std::min(kHungRendererDelay * 8 / 10, kMaxAuthorizationTimeout)));
xzhang3a2a4702017-04-07 16:34:3051
52 if (audio_output->Initialize(sample_rate, frames_per_buffer, client)) {
53 // Balanced by Release invoked in
54 // PepperPlatformAudioOutputDev::ShutDownOnIOThread().
55 audio_output->AddRef();
56 return audio_output.get();
57 }
Ivan Kotenkov2c0d2bb32017-11-01 15:41:2858 return nullptr;
xzhang3a2a4702017-04-07 16:34:3059}
60
61void PepperPlatformAudioOutputDev::RequestDeviceAuthorization() {
62 if (ipc_) {
63 io_task_runner_->PostTask(
64 FROM_HERE,
tzik998f5002017-08-18 05:03:5165 base::BindOnce(
xzhang3a2a4702017-04-07 16:34:3066 &PepperPlatformAudioOutputDev::RequestDeviceAuthorizationOnIOThread,
67 this));
68 }
69}
70
71bool PepperPlatformAudioOutputDev::StartPlayback() {
72 if (ipc_) {
73 io_task_runner_->PostTask(
74 FROM_HERE,
tzik998f5002017-08-18 05:03:5175 base::BindOnce(&PepperPlatformAudioOutputDev::StartPlaybackOnIOThread,
76 this));
xzhang3a2a4702017-04-07 16:34:3077 return true;
78 }
79 return false;
80}
81
82bool PepperPlatformAudioOutputDev::StopPlayback() {
83 if (ipc_) {
84 io_task_runner_->PostTask(
85 FROM_HERE,
tzik998f5002017-08-18 05:03:5186 base::BindOnce(&PepperPlatformAudioOutputDev::StopPlaybackOnIOThread,
87 this));
xzhang3a2a4702017-04-07 16:34:3088 return true;
89 }
90 return false;
91}
92
93bool PepperPlatformAudioOutputDev::SetVolume(double volume) {
94 if (ipc_) {
95 io_task_runner_->PostTask(
96 FROM_HERE,
tzik998f5002017-08-18 05:03:5197 base::BindOnce(&PepperPlatformAudioOutputDev::SetVolumeOnIOThread, this,
98 volume));
xzhang3a2a4702017-04-07 16:34:3099 return true;
100 }
101 return false;
102}
103
104void PepperPlatformAudioOutputDev::ShutDown() {
105 // Called on the main thread to stop all audio callbacks. We must only change
106 // the client on the main thread, and the delegates from the I/O thread.
Ivan Kotenkov2c0d2bb32017-11-01 15:41:28107 client_ = nullptr;
xzhang3a2a4702017-04-07 16:34:30108 io_task_runner_->PostTask(
109 FROM_HERE,
tzik998f5002017-08-18 05:03:51110 base::BindOnce(&PepperPlatformAudioOutputDev::ShutDownOnIOThread, this));
xzhang3a2a4702017-04-07 16:34:30111}
112
113void PepperPlatformAudioOutputDev::OnError() {
114 DCHECK(io_task_runner_->BelongsToCurrentThread());
115
116 // Do nothing if the stream has been closed.
117 if (state_ < CREATING_STREAM)
118 return;
119
120 DLOG(WARNING) << "PepperPlatformAudioOutputDev::OnError()";
121}
122
123void PepperPlatformAudioOutputDev::OnDeviceAuthorized(
124 media::OutputDeviceStatus device_status,
125 const media::AudioParameters& output_params,
126 const std::string& matched_device_id) {
127 DCHECK(io_task_runner_->BelongsToCurrentThread());
128
129 auth_timeout_action_.reset();
130
131 // Do nothing if late authorization is received after timeout.
132 if (state_ == IPC_CLOSED)
133 return;
134
135 LOG_IF(WARNING, device_status == media::OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT)
136 << "Output device authorization timed out";
137
138 DCHECK_EQ(state_, AUTHORIZING);
139
140 // It may happen that a second authorization is received as a result to a
141 // call to StartPlayback() after Shutdown(). If the status for the second
142 // authorization differs from the first, it will not be reflected in
143 // |device_status_| to avoid a race.
144 // This scenario is unlikely. If it occurs, the new value will be
145 // different from OUTPUT_DEVICE_STATUS_OK, so the PepperPlatformAudioOutputDev
146 // will enter the IPC_CLOSED state anyway, which is the safe thing to do.
147 // This is preferable to holding a lock.
148 if (!did_receive_auth_.IsSignaled())
149 device_status_ = device_status;
150
151 if (device_status == media::OUTPUT_DEVICE_STATUS_OK) {
152 state_ = AUTHORIZED;
153 if (!did_receive_auth_.IsSignaled()) {
154 output_params_ = output_params;
155
156 // It's possible to not have a matched device obtained via session id. It
157 // means matching output device through |session_id_| failed and the
158 // default device is used.
159 DCHECK(media::AudioDeviceDescription::UseSessionIdToSelectDevice(
160 session_id_, device_id_) ||
161 matched_device_id_.empty());
162 matched_device_id_ = matched_device_id;
163
164 DVLOG(1) << "PepperPlatformAudioOutputDev authorized, session_id: "
165 << session_id_ << ", device_id: " << device_id_
166 << ", matched_device_id: " << matched_device_id_;
167
168 did_receive_auth_.Signal();
169 }
170 if (start_on_authorized_)
171 CreateStreamOnIOThread(params_);
172 } else {
173 // Closing IPC forces a Signal(), so no clients are locked waiting
174 // indefinitely after this method returns.
175 ipc_->CloseStream();
176 OnIPCClosed();
177 main_task_runner_->PostTask(
178 FROM_HERE,
tzik998f5002017-08-18 05:03:51179 base::BindOnce(
180 &PepperPlatformAudioOutputDev::NotifyStreamCreationFailed, this));
xzhang3a2a4702017-04-07 16:34:30181 }
182}
183
184void PepperPlatformAudioOutputDev::OnStreamCreated(
Alexandr Ilin20f2841c2018-06-01 11:56:18185 base::UnsafeSharedMemoryRegion shared_memory_region,
Robert Sesek8dd06452020-02-19 01:10:49186 base::SyncSocket::ScopedHandle socket_handle,
Max Morin6b683a82018-04-26 09:40:04187 bool playing_automatically) {
Alexandr Ilin20f2841c2018-06-01 11:56:18188 DCHECK(shared_memory_region.IsValid());
Xiaohan Wang0ac63f832022-01-15 00:06:36189#if BUILDFLAG(IS_WIN)
Robert Sesek8dd06452020-02-19 01:10:49190 DCHECK(socket_handle.IsValid());
xzhang3a2a4702017-04-07 16:34:30191#else
Robert Sesek8dd06452020-02-19 01:10:49192 DCHECK(socket_handle.is_valid());
xzhang3a2a4702017-04-07 16:34:30193#endif
Alexandr Ilin20f2841c2018-06-01 11:56:18194 DCHECK_GT(shared_memory_region.GetSize(), 0u);
xzhang3a2a4702017-04-07 16:34:30195
Sean Maher5b9af51f2022-11-21 15:32:47196 if (base::SingleThreadTaskRunner::GetCurrentDefault().get() ==
197 main_task_runner_.get()) {
xzhang3a2a4702017-04-07 16:34:30198 // Must dereference the client only on the main thread. Shutdown may have
199 // occurred while the request was in-flight, so we need to NULL check.
200 if (client_)
Robert Sesek8dd06452020-02-19 01:10:49201 client_->StreamCreated(std::move(shared_memory_region),
202 std::move(socket_handle));
xzhang3a2a4702017-04-07 16:34:30203 } else {
204 DCHECK(io_task_runner_->BelongsToCurrentThread());
205 if (state_ != CREATING_STREAM)
206 return;
207
208 state_ = PAUSED;
209 if (play_on_start_)
210 StartPlaybackOnIOThread();
211
212 main_task_runner_->PostTask(
tzik998f5002017-08-18 05:03:51213 FROM_HERE,
214 base::BindOnce(&PepperPlatformAudioOutputDev::OnStreamCreated, this,
Robert Sesek8dd06452020-02-19 01:10:49215 std::move(shared_memory_region),
216 std::move(socket_handle), playing_automatically));
xzhang3a2a4702017-04-07 16:34:30217 }
218}
219
220void PepperPlatformAudioOutputDev::OnIPCClosed() {
221 DCHECK(io_task_runner_->BelongsToCurrentThread());
222 state_ = IPC_CLOSED;
223 ipc_.reset();
224
225 // Signal to unblock any blocked threads waiting for parameters
226 did_receive_auth_.Signal();
227}
228
229PepperPlatformAudioOutputDev::~PepperPlatformAudioOutputDev() {
230 // Make sure we have been shut down. Warning: this will usually happen on
231 // the I/O thread!
232 DCHECK(!ipc_);
233 DCHECK(!client_);
234}
235
236PepperPlatformAudioOutputDev::PepperPlatformAudioOutputDev(
237 int render_frame_id,
238 const std::string& device_id,
xzhang3a2a4702017-04-07 16:34:30239 base::TimeDelta authorization_timeout)
Ivan Kotenkov2c0d2bb32017-11-01 15:41:28240 : client_(nullptr),
Sean Maher5b9af51f2022-11-21 15:32:47241 main_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
xzhang3a2a4702017-04-07 16:34:30242 io_task_runner_(ChildProcess::current()->io_task_runner()),
243 render_frame_id_(render_frame_id),
244 state_(IDLE),
245 start_on_authorized_(true),
246 play_on_start_(false),
xzhang3a2a4702017-04-07 16:34:30247 device_id_(device_id),
xzhang3a2a4702017-04-07 16:34:30248 did_receive_auth_(base::WaitableEvent::ResetPolicy::MANUAL,
249 base::WaitableEvent::InitialState::NOT_SIGNALED),
250 device_status_(media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL),
251 auth_timeout_(authorization_timeout) {}
252
253bool PepperPlatformAudioOutputDev::Initialize(int sample_rate,
254 int frames_per_buffer,
255 PepperAudioOutputHost* client) {
256 DCHECK(main_task_runner_->BelongsToCurrentThread());
257
258 RenderFrameImpl* const render_frame =
259 RenderFrameImpl::FromRoutingID(render_frame_id_);
260 if (!render_frame || !client)
261 return false;
262
263 client_ = client;
264
deejay.kim21b2beb92021-11-30 23:16:27265 ipc_ = blink::AudioOutputIPCFactory::GetInstance().CreateAudioOutputIPC(
Chris Hamilton5075457e2020-09-01 18:17:37266 render_frame->GetWebFrame()->GetLocalFrameToken());
xzhang3a2a4702017-04-07 16:34:30267 CHECK(ipc_);
268
269 params_.Reset(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
Fredrik Hernqvistc8325f552022-08-31 19:43:04270 media::ChannelLayoutConfig::Stereo(), sample_rate,
271 frames_per_buffer);
xzhang3a2a4702017-04-07 16:34:30272
273 io_task_runner_->PostTask(
274 FROM_HERE,
tzik998f5002017-08-18 05:03:51275 base::BindOnce(&PepperPlatformAudioOutputDev::CreateStreamOnIOThread,
276 this, params_));
xzhang3a2a4702017-04-07 16:34:30277
278 return true;
279}
280
281void PepperPlatformAudioOutputDev::RequestDeviceAuthorizationOnIOThread() {
282 DCHECK(io_task_runner_->BelongsToCurrentThread());
283 DCHECK_EQ(state_, IDLE);
284
285 if (!ipc_)
286 return;
287
288 state_ = AUTHORIZING;
Max Morinf506af592018-04-17 12:23:32289 ipc_->RequestDeviceAuthorization(this, session_id_, device_id_);
xzhang3a2a4702017-04-07 16:34:30290
Xiaohan Wangabff0302021-10-27 00:42:57291 if (auth_timeout_.is_positive()) {
xzhang3a2a4702017-04-07 16:34:30292 // Create the timer on the thread it's used on. It's guaranteed to be
293 // deleted on the same thread since users must call ShutDown() before
294 // deleting PepperPlatformAudioOutputDev; see ShutDownOnIOThread().
Peter Boströmdd7e40ec2021-04-05 20:40:10295 auth_timeout_action_ = std::make_unique<base::OneShotTimer>();
xzhang3a2a4702017-04-07 16:34:30296 auth_timeout_action_->Start(
297 FROM_HERE, auth_timeout_,
tzik2ce14ce2018-07-26 05:17:18298 base::BindOnce(&PepperPlatformAudioOutputDev::OnDeviceAuthorized, this,
299 media::OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT,
300 media::AudioParameters(), std::string()));
xzhang3a2a4702017-04-07 16:34:30301 }
302}
303
304void PepperPlatformAudioOutputDev::CreateStreamOnIOThread(
305 const media::AudioParameters& params) {
306 DCHECK(io_task_runner_->BelongsToCurrentThread());
307 switch (state_) {
308 case IPC_CLOSED:
309 main_task_runner_->PostTask(
310 FROM_HERE,
tzik998f5002017-08-18 05:03:51311 base::BindOnce(
312 &PepperPlatformAudioOutputDev::NotifyStreamCreationFailed, this));
xzhang3a2a4702017-04-07 16:34:30313 break;
314
315 case IDLE:
Max Morinf506af592018-04-17 12:23:32316 if (did_receive_auth_.IsSignaled() && device_id_.empty()) {
xzhang3a2a4702017-04-07 16:34:30317 state_ = CREATING_STREAM;
Olga Sharonova85f771382022-03-31 07:11:25318 ipc_->CreateStream(this, params);
xzhang3a2a4702017-04-07 16:34:30319 } else {
320 RequestDeviceAuthorizationOnIOThread();
321 start_on_authorized_ = true;
322 }
323 break;
324
325 case AUTHORIZING:
326 start_on_authorized_ = true;
327 break;
328
329 case AUTHORIZED:
330 state_ = CREATING_STREAM;
Olga Sharonova85f771382022-03-31 07:11:25331 ipc_->CreateStream(this, params);
xzhang3a2a4702017-04-07 16:34:30332 start_on_authorized_ = false;
333 break;
334
335 case CREATING_STREAM:
336 case PAUSED:
337 case PLAYING:
338 NOTREACHED();
339 break;
340 }
341}
342
343void PepperPlatformAudioOutputDev::StartPlaybackOnIOThread() {
344 DCHECK(io_task_runner_->BelongsToCurrentThread());
345 if (!ipc_)
346 return;
347
348 if (state_ == PAUSED) {
349 ipc_->PlayStream();
350 state_ = PLAYING;
351 play_on_start_ = false;
352 } else {
353 if (state_ < CREATING_STREAM)
354 CreateStreamOnIOThread(params_);
355
356 play_on_start_ = true;
357 }
358}
359
360void PepperPlatformAudioOutputDev::StopPlaybackOnIOThread() {
361 DCHECK(io_task_runner_->BelongsToCurrentThread());
362 if (!ipc_)
363 return;
364
365 if (state_ == PLAYING) {
366 ipc_->PauseStream();
367 state_ = PAUSED;
368 }
369 play_on_start_ = false;
370}
371
372void PepperPlatformAudioOutputDev::SetVolumeOnIOThread(double volume) {
373 DCHECK(io_task_runner_->BelongsToCurrentThread());
374 if (!ipc_)
375 return;
376
377 if (state_ >= CREATING_STREAM)
378 ipc_->SetVolume(volume);
379}
380
381void PepperPlatformAudioOutputDev::ShutDownOnIOThread() {
382 DCHECK(io_task_runner_->BelongsToCurrentThread());
383
384 // Make sure we don't call shutdown more than once.
385 if (!ipc_)
386 return;
387
388 // Close the stream, if we haven't already.
389 if (state_ >= AUTHORIZING) {
390 ipc_->CloseStream();
391 ipc_.reset();
392 state_ = IDLE;
393 }
394 start_on_authorized_ = false;
395
396 // Destoy the timer on the thread it's used on.
397 auth_timeout_action_.reset();
398
399 // Release for the delegate, balances out the reference taken in
400 // PepperPlatformAudioOutputDev::Create.
401 Release();
402}
403
404void PepperPlatformAudioOutputDev::NotifyStreamCreationFailed() {
405 DCHECK(main_task_runner_->BelongsToCurrentThread());
406
407 if (client_)
408 client_->StreamCreationFailed();
409}
410
411} // namespace content