| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/renderer_host/media/audio_renderer_host.h" |
| |
| #include <stdint.h> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/lazy_instance.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "content/browser/bad_message.h" |
| #include "content/browser/browser_main_loop.h" |
| #include "content/browser/media/audio_stream_monitor.h" |
| #include "content/browser/media/capture/audio_mirroring_manager.h" |
| #include "content/browser/media/media_internals.h" |
| #include "content/browser/renderer_host/media/audio_input_device_manager.h" |
| #include "content/browser/renderer_host/media/audio_output_delegate_impl.h" |
| #include "content/browser/renderer_host/media/audio_sync_reader.h" |
| #include "content/browser/renderer_host/media/media_stream_manager.h" |
| #include "content/common/media/audio_messages.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/media_device_id.h" |
| #include "content/public/browser/media_observer.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "media/audio/audio_device_description.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/limits.h" |
| |
| using media::AudioBus; |
| using media::AudioManager; |
| |
| namespace content { |
| |
| namespace { |
| |
| void UMALogDeviceAuthorizationTime(base::TimeTicks auth_start_time) { |
| UMA_HISTOGRAM_CUSTOM_TIMES("Media.Audio.OutputDeviceAuthorizationTime", |
| base::TimeTicks::Now() - auth_start_time, |
| base::TimeDelta::FromMilliseconds(1), |
| base::TimeDelta::FromMilliseconds(5000), 50); |
| } |
| |
| // Check that the routing ID references a valid RenderFrameHost, and run |
| // |callback| on the IO thread with true if the ID is valid. |
| void ValidateRenderFrameId(int render_process_id, |
| int render_frame_id, |
| const base::Callback<void(bool)>& callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| const bool frame_exists = |
| !!RenderFrameHost::FromID(render_process_id, render_frame_id); |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| base::Bind(callback, frame_exists)); |
| } |
| |
| } // namespace |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // AudioRendererHost implementations. |
| |
| AudioRendererHost::AudioRendererHost(int render_process_id, |
| media::AudioManager* audio_manager, |
| AudioMirroringManager* mirroring_manager, |
| MediaStreamManager* media_stream_manager, |
| const std::string& salt) |
| : BrowserMessageFilter(AudioMsgStart), |
| render_process_id_(render_process_id), |
| audio_manager_(audio_manager), |
| mirroring_manager_(mirroring_manager), |
| media_stream_manager_(media_stream_manager), |
| salt_(salt), |
| validate_render_frame_id_function_(&ValidateRenderFrameId), |
| authorization_handler_(audio_manager_, |
| media_stream_manager, |
| render_process_id_, |
| salt) { |
| DCHECK(audio_manager_); |
| } |
| |
| AudioRendererHost::~AudioRendererHost() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(delegates_.empty()); |
| } |
| |
| void AudioRendererHost::GetOutputControllers( |
| const RenderProcessHost::GetAudioOutputControllersCallback& |
| callback) const { |
| BrowserThread::PostTaskAndReplyWithResult( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&AudioRendererHost::DoGetOutputControllers, this), callback); |
| } |
| |
| void AudioRendererHost::OnChannelClosing() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| delegates_.clear(); |
| |
| // Remove any authorizations for streams that were not yet created |
| authorizations_.clear(); |
| } |
| |
| void AudioRendererHost::OnDestruct() const { |
| BrowserThread::DeleteOnIOThread::Destruct(this); |
| } |
| |
| void AudioRendererHost::OnStreamCreated( |
| int stream_id, |
| base::SharedMemory* shared_memory, |
| base::CancelableSyncSocket* foreign_socket) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!PeerHandle()) { |
| DLOG(WARNING) << "Renderer process handle is invalid."; |
| OnStreamError(stream_id); |
| return; |
| } |
| |
| if (!LookupById(stream_id)) { |
| OnStreamError(stream_id); |
| return; |
| } |
| |
| base::SharedMemoryHandle foreign_memory_handle; |
| base::SyncSocket::TransitDescriptor socket_descriptor; |
| size_t shared_memory_size = shared_memory->requested_size(); |
| |
| if (!(shared_memory->ShareToProcess(PeerHandle(), &foreign_memory_handle) && |
| foreign_socket->PrepareTransitDescriptor(PeerHandle(), |
| &socket_descriptor))) { |
| // Something went wrong in preparing the IPC handles. |
| OnStreamError(stream_id); |
| return; |
| } |
| |
| Send(new AudioMsg_NotifyStreamCreated( |
| stream_id, foreign_memory_handle, socket_descriptor, |
| base::checked_cast<uint32_t>(shared_memory_size))); |
| } |
| |
| void AudioRendererHost::OnStreamError(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| SendErrorMessage(stream_id); |
| OnCloseStream(stream_id); |
| } |
| |
| void AudioRendererHost::DidValidateRenderFrame(int stream_id, bool is_valid) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (is_valid) |
| return; |
| |
| DLOG(WARNING) << "Render frame for stream (id=" << stream_id |
| << ") no longer exists."; |
| OnStreamError(stream_id); |
| } |
| |
| RenderProcessHost::AudioOutputControllerList |
| AudioRendererHost::DoGetOutputControllers() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| RenderProcessHost::AudioOutputControllerList controllers; |
| for (const auto& delegate : delegates_) |
| controllers.push_back(delegate->GetController()); |
| |
| return controllers; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // IPC Messages handler |
| bool AudioRendererHost::OnMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(AudioRendererHost, message) |
| IPC_MESSAGE_HANDLER(AudioHostMsg_RequestDeviceAuthorization, |
| OnRequestDeviceAuthorization) |
| IPC_MESSAGE_HANDLER(AudioHostMsg_CreateStream, OnCreateStream) |
| IPC_MESSAGE_HANDLER(AudioHostMsg_PlayStream, OnPlayStream) |
| IPC_MESSAGE_HANDLER(AudioHostMsg_PauseStream, OnPauseStream) |
| IPC_MESSAGE_HANDLER(AudioHostMsg_CloseStream, OnCloseStream) |
| IPC_MESSAGE_HANDLER(AudioHostMsg_SetVolume, OnSetVolume) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| |
| return handled; |
| } |
| |
| void AudioRendererHost::OnRequestDeviceAuthorization( |
| int stream_id, |
| int render_frame_id, |
| int session_id, |
| const std::string& device_id, |
| const url::Origin& security_origin) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| const base::TimeTicks auth_start_time = base::TimeTicks::Now(); |
| DVLOG(1) << "AudioRendererHost@" << this << "::OnRequestDeviceAuthorization" |
| << "(stream_id=" << stream_id |
| << ", render_frame_id=" << render_frame_id |
| << ", session_id=" << session_id << ", device_id=" << device_id |
| << ", security_origin=" << security_origin << ")"; |
| |
| if (LookupById(stream_id) || IsAuthorizationStarted(stream_id)) |
| return; |
| |
| authorizations_.insert( |
| std::make_pair(stream_id, std::make_pair(false, std::string()))); |
| |
| // Unretained is ok here since |this| owns |authorization_handler_| and |
| // |authorization_handler_| owns the callback. |
| authorization_handler_.RequestDeviceAuthorization( |
| render_frame_id, session_id, device_id, security_origin, |
| base::Bind(&AudioRendererHost::AuthorizationCompleted, |
| base::Unretained(this), stream_id, security_origin, |
| auth_start_time)); |
| } |
| |
| void AudioRendererHost::AuthorizationCompleted( |
| int stream_id, |
| const url::Origin& security_origin, |
| base::TimeTicks auth_start_time, |
| media::OutputDeviceStatus status, |
| bool should_send_id, |
| const media::AudioParameters& params, |
| const std::string& raw_device_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| auto auth_data = authorizations_.find(stream_id); |
| if (auth_data == authorizations_.end()) |
| return; // Stream was closed before finishing authorization |
| |
| UMALogDeviceAuthorizationTime(auth_start_time); |
| if (status == media::OUTPUT_DEVICE_STATUS_OK) { |
| auth_data->second.first = true; |
| auth_data->second.second = raw_device_id; |
| if (should_send_id) { |
| std::string hashed_id = MediaStreamManager::GetHMACForMediaDeviceID( |
| salt_, security_origin, raw_device_id); |
| Send(new AudioMsg_NotifyDeviceAuthorized(stream_id, status, params, |
| hashed_id)); |
| } else { |
| Send(new AudioMsg_NotifyDeviceAuthorized(stream_id, status, params, |
| std::string())); |
| } |
| } else { |
| authorizations_.erase(auth_data); |
| Send(new AudioMsg_NotifyDeviceAuthorized( |
| stream_id, status, media::AudioParameters::UnavailableDeviceParams(), |
| std::string())); |
| } |
| } |
| |
| void AudioRendererHost::OnCreateStream(int stream_id, |
| int render_frame_id, |
| const media::AudioParameters& params) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DVLOG(1) << "AudioRendererHost@" << this << "::OnCreateStream" |
| << "(stream_id=" << stream_id << ")"; |
| |
| // Determine whether to use the device_unique_id from an authorization, or an |
| // empty string (i.e., when no previous authorization was requested, assume |
| // default device). |
| std::string device_unique_id; |
| const auto& auth_data = authorizations_.find(stream_id); |
| if (auth_data != authorizations_.end()) { |
| if (!auth_data->second.first) { |
| // The authorization for this stream is still pending, so it's an error |
| // to create it now. |
| content::bad_message::ReceivedBadMessage( |
| this, bad_message::ARH_CREATED_STREAM_WITHOUT_AUTHORIZATION); |
| return; |
| } |
| device_unique_id.swap(auth_data->second.second); |
| authorizations_.erase(auth_data); |
| } |
| |
| // Fail early if either of two sanity-checks fail: |
| // 1. There should not yet exist an AudioOutputDelegate for the given |
| // |stream_id| since the renderer may not create two streams with the |
| // same ID. |
| // 2. An out-of-range render frame ID was provided. Renderers must *always* |
| // specify a valid render frame ID for each audio output they create, as |
| // several browser-level features depend on this (e.g., OOM manager, UI |
| // audio indicator, muting, audio capture). |
| // Note: media::AudioParameters is validated in the deserializer, so there is |
| // no need to check that here. |
| if (LookupById(stream_id)) { |
| SendErrorMessage(stream_id); |
| return; |
| } |
| if (render_frame_id <= 0) { |
| SendErrorMessage(stream_id); |
| return; |
| } |
| |
| // Post a task to the UI thread to check that the |render_frame_id| references |
| // a valid render frame. This validation is important for all the reasons |
| // stated in the comments above. This does not block stream creation, but will |
| // force-close the stream later if validation fails. |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(validate_render_frame_id_function_, render_process_id_, |
| render_frame_id, |
| base::Bind(&AudioRendererHost::DidValidateRenderFrame, this, |
| stream_id))); |
| |
| MediaObserver* const media_observer = |
| GetContentClient()->browser()->GetMediaObserver(); |
| |
| MediaInternals* const media_internals = MediaInternals::GetInstance(); |
| std::unique_ptr<media::AudioLog> audio_log = media_internals->CreateAudioLog( |
| media::AudioLogFactory::AUDIO_OUTPUT_CONTROLLER); |
| media_internals->SetWebContentsTitleForAudioLogEntry( |
| stream_id, render_process_id_, render_frame_id, audio_log.get()); |
| delegates_.push_back( |
| base::WrapUnique<AudioOutputDelegate>(new AudioOutputDelegateImpl( |
| this, audio_manager_, std::move(audio_log), mirroring_manager_, |
| media_observer, stream_id, render_frame_id, render_process_id_, |
| params, device_unique_id))); |
| } |
| |
| void AudioRendererHost::OnPlayStream(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| AudioOutputDelegate* delegate = LookupById(stream_id); |
| if (!delegate) { |
| SendErrorMessage(stream_id); |
| return; |
| } |
| |
| delegate->OnPlayStream(); |
| } |
| |
| void AudioRendererHost::OnPauseStream(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| AudioOutputDelegate* delegate = LookupById(stream_id); |
| if (!delegate) { |
| SendErrorMessage(stream_id); |
| return; |
| } |
| |
| delegate->OnPauseStream(); |
| } |
| |
| void AudioRendererHost::OnSetVolume(int stream_id, double volume) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| AudioOutputDelegate* delegate = LookupById(stream_id); |
| if (!delegate) { |
| SendErrorMessage(stream_id); |
| return; |
| } |
| |
| // Make sure the volume is valid. |
| if (volume < 0 || volume > 1.0) |
| return; |
| delegate->OnSetVolume(volume); |
| } |
| |
| void AudioRendererHost::SendErrorMessage(int stream_id) { |
| Send(new AudioMsg_NotifyStreamError(stream_id)); |
| } |
| |
| void AudioRendererHost::OnCloseStream(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| authorizations_.erase(stream_id); |
| |
| auto i = LookupIteratorById(stream_id); |
| |
| // Prevent oustanding callbacks from attempting to close/delete the same |
| // AudioOutputDelegate twice. |
| if (i == delegates_.end()) |
| return; |
| |
| std::swap(*i, delegates_.back()); |
| delegates_.pop_back(); |
| } |
| |
| AudioRendererHost::AudioOutputDelegateVector::iterator |
| AudioRendererHost::LookupIteratorById(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| return std::find_if( |
| delegates_.begin(), delegates_.end(), |
| [stream_id](const std::unique_ptr<AudioOutputDelegate>& d) { |
| return d->GetStreamId() == stream_id; |
| }); |
| } |
| |
| AudioOutputDelegate* AudioRendererHost::LookupById(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| auto i = LookupIteratorById(stream_id); |
| return i != delegates_.end() ? i->get() : nullptr; |
| } |
| |
| bool AudioRendererHost::IsAuthorizationStarted(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return authorizations_.find(stream_id) != authorizations_.end(); |
| } |
| |
| void AudioRendererHost::OverrideDevicePermissionsForTesting(bool has_access) { |
| authorization_handler_.OverridePermissionsForTesting(has_access); |
| } |
| } // namespace content |