OLD | NEW |
(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 |
OLD | NEW |