diff options
Diffstat (limited to 'chromium/media/audio/audio_output_device.cc')
-rw-r--r-- | chromium/media/audio/audio_output_device.cc | 230 |
1 files changed, 165 insertions, 65 deletions
diff --git a/chromium/media/audio/audio_output_device.cc b/chromium/media/audio/audio_output_device.cc index 3213400ff34..8452bd65d8e 100644 --- a/chromium/media/audio/audio_output_device.cc +++ b/chromium/media/audio/audio_output_device.cc @@ -4,8 +4,6 @@ #include "media/audio/audio_output_device.h" -#include <string> - #include "base/callback_helpers.h" #include "base/threading/thread_restrictions.h" #include "base/time/time.h" @@ -41,38 +39,42 @@ class AudioOutputDevice::AudioThreadCallback AudioOutputDevice::AudioOutputDevice( scoped_ptr<AudioOutputIPC> ipc, - const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner) + const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, + int session_id, + const std::string& device_id, + const url::Origin& security_origin) : ScopedTaskRunnerObserver(io_task_runner), callback_(NULL), ipc_(ipc.Pass()), state_(IDLE), + start_on_authorized_(false), play_on_start_(true), - session_id_(-1), + session_id_(session_id), + device_id_(device_id), + security_origin_(security_origin), stopping_hack_(false), - current_switch_request_id_(0) { + switch_output_device_on_start_(false), + did_receive_auth_(true, false), + device_status_(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL) { CHECK(ipc_); // The correctness of the code depends on the relative values assigned in the // State enum. static_assert(IPC_CLOSED < IDLE, "invalid enum value assignment 0"); - static_assert(IDLE < CREATING_STREAM, "invalid enum value assignment 1"); - static_assert(CREATING_STREAM < PAUSED, "invalid enum value assignment 2"); - static_assert(PAUSED < PLAYING, "invalid enum value assignment 3"); + static_assert(IDLE < AUTHORIZING, "invalid enum value assignment 1"); + static_assert(AUTHORIZING < AUTHORIZED, "invalid enum value assignment 2"); + static_assert(AUTHORIZED < CREATING_STREAM, + "invalid enum value assignment 3"); + static_assert(CREATING_STREAM < PAUSED, "invalid enum value assignment 4"); + static_assert(PAUSED < PLAYING, "invalid enum value assignment 5"); } -void AudioOutputDevice::InitializeWithSessionId(const AudioParameters& params, - RenderCallback* callback, - int session_id) { - DCHECK(!callback_) << "Calling InitializeWithSessionId() twice?"; +void AudioOutputDevice::Initialize(const AudioParameters& params, + RenderCallback* callback) { + DCHECK(!callback_) << "Calling Initialize() twice?"; DCHECK(params.IsValid()); audio_parameters_ = params; callback_ = callback; - session_id_ = session_id; -} - -void AudioOutputDevice::Initialize(const AudioParameters& params, - RenderCallback* callback) { - InitializeWithSessionId(params, callback, 0); } AudioOutputDevice::~AudioOutputDevice() { @@ -84,11 +86,18 @@ AudioOutputDevice::~AudioOutputDevice() { // its bound parameters in the correct thread instead of implicitly releasing // them in the thread where this destructor runs. if (!current_switch_callback_.is_null()) { - base::ResetAndReturn(¤t_switch_callback_).Run( - SWITCH_OUTPUT_DEVICE_RESULT_ERROR_OBSOLETE); + base::ResetAndReturn(¤t_switch_callback_) + .Run(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL); } } +void AudioOutputDevice::RequestDeviceAuthorization() { + task_runner()->PostTask( + FROM_HERE, + base::Bind(&AudioOutputDevice::RequestDeviceAuthorizationOnIOThread, + this)); +} + void AudioOutputDevice::Start() { DCHECK(callback_) << "Initialize hasn't been called"; task_runner()->PostTask(FROM_HERE, @@ -129,21 +138,73 @@ bool AudioOutputDevice::SetVolume(double volume) { return true; } +OutputDevice* AudioOutputDevice::GetOutputDevice() { + return this; +} + void AudioOutputDevice::SwitchOutputDevice( const std::string& device_id, - const GURL& security_origin, + const url::Origin& security_origin, const SwitchOutputDeviceCB& callback) { - DVLOG(1) << __FUNCTION__ << "(" << device_id << ")"; task_runner()->PostTask( FROM_HERE, base::Bind(&AudioOutputDevice::SwitchOutputDeviceOnIOThread, this, device_id, security_origin, callback)); } +AudioParameters AudioOutputDevice::GetOutputParameters() { + CHECK(!task_runner()->BelongsToCurrentThread()); + did_receive_auth_.Wait(); + return output_params_; +} + +OutputDeviceStatus AudioOutputDevice::GetDeviceStatus() { + CHECK(!task_runner()->BelongsToCurrentThread()); + did_receive_auth_.Wait(); + return device_status_; +} + +void AudioOutputDevice::RequestDeviceAuthorizationOnIOThread() { + DCHECK(task_runner()->BelongsToCurrentThread()); + DCHECK_EQ(state_, IDLE); + state_ = AUTHORIZING; + ipc_->RequestDeviceAuthorization(this, session_id_, device_id_, + security_origin_); +} + void AudioOutputDevice::CreateStreamOnIOThread(const AudioParameters& params) { DCHECK(task_runner()->BelongsToCurrentThread()); - if (state_ == IDLE) { - state_ = CREATING_STREAM; - ipc_->CreateStream(this, params, session_id_); + switch (state_) { + case IPC_CLOSED: + if (callback_) + callback_->OnRenderError(); + break; + + case IDLE: + if (did_receive_auth_.IsSignaled() && device_id_.empty() && + security_origin_.unique()) { + state_ = CREATING_STREAM; + ipc_->CreateStream(this, params); + } else { + RequestDeviceAuthorizationOnIOThread(); + start_on_authorized_ = true; + } + break; + + case AUTHORIZING: + start_on_authorized_ = true; + break; + + case AUTHORIZED: + state_ = CREATING_STREAM; + ipc_->CreateStream(this, params); + start_on_authorized_ = false; + break; + + case CREATING_STREAM: + case PAUSED: + case PLAYING: + NOTREACHED(); + break; } } @@ -175,10 +236,11 @@ void AudioOutputDevice::ShutDownOnIOThread() { DCHECK(task_runner()->BelongsToCurrentThread()); // Close the stream, if we haven't already. - if (state_ >= CREATING_STREAM) { + if (state_ >= AUTHORIZING) { ipc_->CloseStream(); state_ = IDLE; } + start_on_authorized_ = false; // We can run into an issue where ShutDownOnIOThread is called right after // OnStreamCreated is called in cases where Start/Stop are called before we @@ -203,16 +265,24 @@ void AudioOutputDevice::SetVolumeOnIOThread(double volume) { void AudioOutputDevice::SwitchOutputDeviceOnIOThread( const std::string& device_id, - const GURL& security_origin, + const url::Origin& security_origin, const SwitchOutputDeviceCB& callback) { DCHECK(task_runner()->BelongsToCurrentThread()); - DVLOG(1) << __FUNCTION__ << "(" << device_id << "," << security_origin << ")"; + + // Do not allow concurrent SwitchOutputDevice requests + if (!current_switch_callback_.is_null()) { + callback.Run(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL); + return; + } + + current_switch_callback_ = callback; + current_switch_device_id_ = device_id; + current_switch_security_origin_ = security_origin; if (state_ >= CREATING_STREAM) { - SetCurrentSwitchRequest(callback); - ipc_->SwitchOutputDevice(device_id, security_origin, - current_switch_request_id_); + ipc_->SwitchOutputDevice(current_switch_device_id_, + current_switch_security_origin_); } else { - callback.Run(SWITCH_OUTPUT_DEVICE_RESULT_ERROR_NOT_SUPPORTED); + switch_output_device_on_start_ = true; } } @@ -246,6 +316,41 @@ void AudioOutputDevice::OnStateChanged(AudioOutputIPCDelegateState state) { } } +void AudioOutputDevice::OnDeviceAuthorized( + OutputDeviceStatus device_status, + const media::AudioParameters& output_params) { + DCHECK(task_runner()->BelongsToCurrentThread()); + DCHECK_EQ(state_, AUTHORIZING); + + // It may happen that a second authorization is received as a result to a + // call to Start() after Stop(). If the status for the second authorization + // differs from the first, it will not be reflected in |device_status_| + // to avoid a race. + // This scenario is unlikely. If it occurs, the new value will be + // different from OUTPUT_DEVICE_STATUS_OK, so the AudioOutputDevice + // will enter the IPC_CLOSED state anyway, which is the safe thing to do. + // This is preferable to holding a lock. + if (!did_receive_auth_.IsSignaled()) + device_status_ = device_status; + + if (device_status == OUTPUT_DEVICE_STATUS_OK) { + state_ = AUTHORIZED; + if (!did_receive_auth_.IsSignaled()) { + output_params_ = output_params; + did_receive_auth_.Signal(); + } + if (start_on_authorized_) + CreateStreamOnIOThread(audio_parameters_); + } else { + // Closing IPC forces a Signal(), so no clients are locked waiting + // indefinitely after this method returns. + ipc_->CloseStream(); + OnIPCClosed(); + if (callback_) + callback_->OnRenderError(); + } +} + void AudioOutputDevice::OnStreamCreated( base::SharedMemoryHandle handle, base::SyncSocket::Handle socket_handle, @@ -274,46 +379,38 @@ void AudioOutputDevice::OnStreamCreated( // delete as they see fit. AudioOutputDevice should internally use WeakPtr // to handle teardown and thread hopping. See http://crbug.com/151051 for // details. - base::AutoLock auto_lock(audio_thread_lock_); - if (stopping_hack_) - return; + { + base::AutoLock auto_lock(audio_thread_lock_); + if (stopping_hack_) + return; + + DCHECK(audio_thread_.IsStopped()); + audio_callback_.reset(new AudioOutputDevice::AudioThreadCallback( + audio_parameters_, handle, length, callback_)); + audio_thread_.Start(audio_callback_.get(), socket_handle, + "AudioOutputDevice", true); + state_ = PAUSED; - DCHECK(audio_thread_.IsStopped()); - audio_callback_.reset(new AudioOutputDevice::AudioThreadCallback( - audio_parameters_, handle, length, callback_)); - audio_thread_.Start( - audio_callback_.get(), socket_handle, "AudioOutputDevice", true); - state_ = PAUSED; - - // We handle the case where Play() and/or Pause() may have been called - // multiple times before OnStreamCreated() gets called. - if (play_on_start_) - PlayOnIOThread(); -} + // We handle the case where Play() and/or Pause() may have been called + // multiple times before OnStreamCreated() gets called. + if (play_on_start_) + PlayOnIOThread(); + } -void AudioOutputDevice::SetCurrentSwitchRequest( - const SwitchOutputDeviceCB& callback) { - DCHECK(task_runner()->BelongsToCurrentThread()); - DVLOG(1) << __FUNCTION__; - // If there is a previous unresolved request, resolve it as obsolete - if (!current_switch_callback_.is_null()) { - base::ResetAndReturn(¤t_switch_callback_).Run( - SWITCH_OUTPUT_DEVICE_RESULT_ERROR_OBSOLETE); + if (switch_output_device_on_start_) { + ipc_->SwitchOutputDevice(current_switch_device_id_, + current_switch_security_origin_); } - current_switch_callback_ = callback; - current_switch_request_id_++; } -void AudioOutputDevice::OnOutputDeviceSwitched( - int request_id, - SwitchOutputDeviceResult result) { +void AudioOutputDevice::OnOutputDeviceSwitched(OutputDeviceStatus result) { DCHECK(task_runner()->BelongsToCurrentThread()); - DCHECK(request_id <= current_switch_request_id_); - DVLOG(1) << __FUNCTION__ - << "(" << request_id << ", " << result << ")"; - if (request_id != current_switch_request_id_) { - return; + if (result == OUTPUT_DEVICE_STATUS_OK) { + session_id_ = 0; // Output device is no longer attached to an input device + device_id_ = current_switch_device_id_; + security_origin_ = current_switch_security_origin_; } + DCHECK(!current_switch_callback_.is_null()); base::ResetAndReturn(¤t_switch_callback_).Run(result); } @@ -321,6 +418,9 @@ void AudioOutputDevice::OnIPCClosed() { DCHECK(task_runner()->BelongsToCurrentThread()); state_ = IPC_CLOSED; ipc_.reset(); + + // Signal to unblock any blocked threads waiting for parameters + did_receive_auth_.Signal(); } void AudioOutputDevice::WillDestroyCurrentMessageLoop() { |