| // 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 "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/shared_memory.h" |
| #include "base/metrics/histogram.h" |
| #include "base/process/process.h" |
| #include "content/browser/bad_message.h" |
| #include "content/browser/browser_main_loop.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/loader/resource_dispatcher_host_impl.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_sync_reader.h" |
| #include "content/browser/renderer_host/media/media_stream_manager.h" |
| #include "content/browser/renderer_host/media/media_stream_ui_proxy.h" |
| #include "content/browser/renderer_host/render_widget_host_impl.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 "content/public/browser/render_view_host.h" |
| #include "content/public/common/content_switches.h" |
| #include "media/audio/audio_device_name.h" |
| #include "media/audio/audio_manager_base.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/limits.h" |
| |
| using media::AudioBus; |
| using media::AudioManager; |
| |
| namespace content { |
| |
| namespace { |
| // TODO(aiolos): This is a temporary hack until the resource scheduler is |
| // migrated to RenderFrames for the Site Isolation project. It's called in |
| // response to low frequency playback state changes. http://crbug.com/472869 |
| int RenderFrameIdToRenderViewId(int render_process_id, int render_frame_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| RenderFrameHost* const frame = |
| RenderFrameHost::FromID(render_process_id, render_frame_id); |
| return frame ? frame->GetRenderViewHost()->GetRoutingID() : MSG_ROUTING_NONE; |
| } |
| |
| void NotifyResourceDispatcherOfAudioStateChange(int render_process_id, |
| bool is_playing, |
| int render_view_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (render_view_id == MSG_ROUTING_NONE || !ResourceDispatcherHostImpl::Get()) |
| return; |
| |
| ResourceDispatcherHostImpl::Get()->OnAudioRenderHostStreamStateChanged( |
| render_process_id, render_view_id, is_playing); |
| } |
| |
| media::AudioParameters DummyParams() { |
| return media::AudioParameters(media::AudioParameters::AUDIO_PCM_LINEAR, |
| media::CHANNEL_LAYOUT_STEREO, |
| media::limits::kMinSampleRate, 1, 1); |
| } |
| |
| std::pair<int, std::pair<bool, std::string>> MakeAuthorizationData( |
| int stream_id, |
| bool authorized, |
| const std::string& device_unique_id) { |
| return std::make_pair(stream_id, |
| std::make_pair(authorized, device_unique_id)); |
| } |
| |
| GURL ConvertToGURL(const url::Origin& origin) { |
| return origin.unique() ? GURL() : GURL(origin.Serialize()); |
| } |
| |
| bool IsValidDeviceId(const std::string& device_id) { |
| static const std::string::size_type kValidLength = 64; |
| |
| if (device_id.empty() || |
| device_id == media::AudioManagerBase::kDefaultDeviceId || |
| device_id == media::AudioManagerBase::kCommunicationsDeviceId) { |
| return true; |
| } |
| |
| if (device_id.length() != kValidLength) |
| return false; |
| |
| for (const char& c : device_id) { |
| if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::string TranslateDefaultId(const std::string& device_id) { |
| return device_id.empty() ? media::AudioManagerBase::kDefaultDeviceId |
| : device_id; |
| } |
| |
| } // namespace |
| |
| class AudioRendererHost::AudioEntry |
| : public media::AudioOutputController::EventHandler { |
| public: |
| AudioEntry(AudioRendererHost* host, |
| int stream_id, |
| int render_frame_id, |
| const media::AudioParameters& params, |
| const std::string& output_device_id, |
| scoped_ptr<base::SharedMemory> shared_memory, |
| scoped_ptr<media::AudioOutputController::SyncReader> reader); |
| ~AudioEntry() override; |
| |
| int stream_id() const { |
| return stream_id_; |
| } |
| |
| int render_frame_id() const { return render_frame_id_; } |
| |
| media::AudioOutputController* controller() const { return controller_.get(); } |
| |
| base::SharedMemory* shared_memory() { |
| return shared_memory_.get(); |
| } |
| |
| media::AudioOutputController::SyncReader* reader() const { |
| return reader_.get(); |
| } |
| |
| bool playing() const { return playing_; } |
| void set_playing(bool playing) { playing_ = playing; } |
| |
| private: |
| // media::AudioOutputController::EventHandler implementation. |
| void OnCreated() override; |
| void OnPlaying() override; |
| void OnPaused() override; |
| void OnError() override; |
| |
| AudioRendererHost* const host_; |
| const int stream_id_; |
| |
| // The routing ID of the source RenderFrame. |
| const int render_frame_id_; |
| |
| // Shared memory for transmission of the audio data. Used by |reader_|. |
| const scoped_ptr<base::SharedMemory> shared_memory_; |
| |
| // The synchronous reader to be used by |controller_|. |
| const scoped_ptr<media::AudioOutputController::SyncReader> reader_; |
| |
| // The AudioOutputController that manages the audio stream. |
| const scoped_refptr<media::AudioOutputController> controller_; |
| |
| bool playing_; |
| }; |
| |
| AudioRendererHost::AudioEntry::AudioEntry( |
| AudioRendererHost* host, |
| int stream_id, |
| int render_frame_id, |
| const media::AudioParameters& params, |
| const std::string& output_device_id, |
| scoped_ptr<base::SharedMemory> shared_memory, |
| scoped_ptr<media::AudioOutputController::SyncReader> reader) |
| : host_(host), |
| stream_id_(stream_id), |
| render_frame_id_(render_frame_id), |
| shared_memory_(shared_memory.Pass()), |
| reader_(reader.Pass()), |
| controller_(media::AudioOutputController::Create(host->audio_manager_, |
| this, |
| params, |
| output_device_id, |
| reader_.get())), |
| playing_(false) { |
| DCHECK(controller_.get()); |
| } |
| |
| AudioRendererHost::AudioEntry::~AudioEntry() {} |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // AudioRendererHost implementations. |
| |
| AudioRendererHost::AudioRendererHost( |
| int render_process_id, |
| media::AudioManager* audio_manager, |
| AudioMirroringManager* mirroring_manager, |
| MediaInternals* media_internals, |
| MediaStreamManager* media_stream_manager, |
| const ResourceContext::SaltCallback& salt_callback) |
| : BrowserMessageFilter(AudioMsgStart), |
| render_process_id_(render_process_id), |
| audio_manager_(audio_manager), |
| mirroring_manager_(mirroring_manager), |
| audio_log_(media_internals->CreateAudioLog( |
| media::AudioLogFactory::AUDIO_OUTPUT_CONTROLLER)), |
| media_stream_manager_(media_stream_manager), |
| num_playing_streams_(0), |
| salt_callback_(salt_callback) { |
| DCHECK(audio_manager_); |
| DCHECK(media_stream_manager_); |
| } |
| |
| AudioRendererHost::~AudioRendererHost() { |
| DCHECK(audio_entries_.empty()); |
| } |
| |
| void AudioRendererHost::GetOutputControllers( |
| const RenderProcessHost::GetAudioOutputControllersCallback& |
| callback) const { |
| BrowserThread::PostTaskAndReplyWithResult( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&AudioRendererHost::DoGetOutputControllers, this), callback); |
| } |
| |
| void AudioRendererHost::OnChannelClosing() { |
| // Since the IPC sender is gone, close all requested audio streams. |
| while (!audio_entries_.empty()) { |
| // Note: OnCloseStream() removes the entries from audio_entries_. |
| OnCloseStream(audio_entries_.begin()->first); |
| } |
| |
| // Remove any authorizations for streams that were not yet created |
| authorizations_.clear(); |
| } |
| |
| void AudioRendererHost::OnDestruct() const { |
| BrowserThread::DeleteOnIOThread::Destruct(this); |
| } |
| |
| void AudioRendererHost::AudioEntry::OnCreated() { |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&AudioRendererHost::DoCompleteCreation, host_, stream_id_)); |
| } |
| |
| void AudioRendererHost::AudioEntry::OnPlaying() { |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&AudioRendererHost::DoNotifyStreamStateChanged, |
| host_, |
| stream_id_, |
| true)); |
| } |
| |
| void AudioRendererHost::AudioEntry::OnPaused() { |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&AudioRendererHost::DoNotifyStreamStateChanged, |
| host_, |
| stream_id_, |
| false)); |
| } |
| |
| void AudioRendererHost::AudioEntry::OnError() { |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&AudioRendererHost::ReportErrorAndClose, host_, stream_id_)); |
| } |
| |
| void AudioRendererHost::DoCompleteCreation(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!PeerHandle()) { |
| DLOG(WARNING) << "Renderer process handle is invalid."; |
| ReportErrorAndClose(stream_id); |
| return; |
| } |
| |
| AudioEntry* const entry = LookupById(stream_id); |
| if (!entry) { |
| ReportErrorAndClose(stream_id); |
| return; |
| } |
| |
| // Once the audio stream is created then complete the creation process by |
| // mapping shared memory and sharing with the renderer process. |
| base::SharedMemoryHandle foreign_memory_handle; |
| if (!entry->shared_memory()->ShareToProcess(PeerHandle(), |
| &foreign_memory_handle)) { |
| // If we failed to map and share the shared memory then close the audio |
| // stream and send an error message. |
| ReportErrorAndClose(entry->stream_id()); |
| return; |
| } |
| |
| AudioSyncReader* reader = static_cast<AudioSyncReader*>(entry->reader()); |
| |
| base::SyncSocket::TransitDescriptor socket_descriptor; |
| |
| // If we failed to prepare the sync socket for the renderer then we fail |
| // the construction of audio stream. |
| if (!reader->PrepareForeignSocket(PeerHandle(), &socket_descriptor)) { |
| ReportErrorAndClose(entry->stream_id()); |
| return; |
| } |
| |
| Send(new AudioMsg_NotifyStreamCreated( |
| entry->stream_id(), foreign_memory_handle, socket_descriptor, |
| entry->shared_memory()->requested_size())); |
| } |
| |
| void AudioRendererHost::DoNotifyStreamStateChanged(int stream_id, |
| bool is_playing) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| AudioEntry* const entry = LookupById(stream_id); |
| if (!entry) |
| return; |
| |
| Send(new AudioMsg_NotifyStreamStateChanged( |
| stream_id, |
| is_playing ? media::AUDIO_OUTPUT_IPC_DELEGATE_STATE_PLAYING |
| : media::AUDIO_OUTPUT_IPC_DELEGATE_STATE_PAUSED)); |
| |
| if (is_playing) { |
| AudioStreamMonitor::StartMonitoringStream( |
| render_process_id_, |
| entry->render_frame_id(), |
| entry->stream_id(), |
| base::Bind(&media::AudioOutputController::ReadCurrentPowerAndClip, |
| entry->controller())); |
| } else { |
| AudioStreamMonitor::StopMonitoringStream( |
| render_process_id_, entry->render_frame_id(), entry->stream_id()); |
| } |
| UpdateNumPlayingStreams(entry, is_playing); |
| } |
| |
| RenderProcessHost::AudioOutputControllerList |
| AudioRendererHost::DoGetOutputControllers() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| RenderProcessHost::AudioOutputControllerList controllers; |
| for (AudioEntryMap::const_iterator it = audio_entries_.begin(); |
| it != audio_entries_.end(); |
| ++it) { |
| controllers.push_back(it->second->controller()); |
| } |
| |
| 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_HANDLER(AudioHostMsg_SwitchOutputDevice, OnSwitchOutputDevice) |
| 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); |
| 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; |
| |
| if (!IsValidDeviceId(device_id)) { |
| Send(new AudioMsg_NotifyDeviceAuthorized(stream_id, false, DummyParams())); |
| return; |
| } |
| |
| // If attempting to use the output device associated to an opened input |
| // device and the output device is found, reuse the input device |
| // permissions. |
| if (session_id != 0) { |
| const StreamDeviceInfo* info = |
| media_stream_manager_->audio_input_device_manager() |
| ->GetOpenedDeviceInfoById(session_id); |
| if (info) { |
| media::AudioParameters output_params( |
| media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| static_cast<media::ChannelLayout>( |
| info->device.matched_output.channel_layout), |
| info->device.matched_output.sample_rate, 16, |
| info->device.matched_output.frames_per_buffer); |
| output_params.set_effects(info->device.matched_output.effects); |
| authorizations_.insert(MakeAuthorizationData( |
| stream_id, true, info->device.matched_output_device_id)); |
| Send(new AudioMsg_NotifyDeviceAuthorized(stream_id, true, output_params)); |
| return; |
| } |
| } |
| |
| authorizations_.insert( |
| MakeAuthorizationData(stream_id, false, std::string())); |
| GURL gurl_security_origin = ConvertToGURL(security_origin); |
| CheckOutputDeviceAccess( |
| render_frame_id, device_id, gurl_security_origin, |
| base::Bind(&AudioRendererHost::OnDeviceAuthorized, this, stream_id, |
| device_id, gurl_security_origin)); |
| } |
| |
| void AudioRendererHost::OnDeviceAuthorized(int stream_id, |
| const std::string& device_id, |
| const GURL& gurl_security_origin, |
| bool have_access) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| const auto& auth_data = authorizations_.find(stream_id); |
| |
| // A close request was received while access check was in progress. |
| if (auth_data == authorizations_.end()) |
| return; |
| |
| if (!have_access) { |
| authorizations_.erase(auth_data); |
| Send(new AudioMsg_NotifyDeviceAuthorized(stream_id, false, DummyParams())); |
| return; |
| } |
| |
| media_stream_manager_->audio_output_device_enumerator()->Enumerate(base::Bind( |
| &AudioRendererHost::TranslateDeviceID, this, device_id, |
| gurl_security_origin, |
| base::Bind(&AudioRendererHost::OnDeviceIDTranslated, this, stream_id))); |
| } |
| |
| void AudioRendererHost::OnDeviceIDTranslated( |
| int stream_id, |
| bool device_found, |
| const AudioOutputDeviceInfo& device_info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| const auto& auth_data = authorizations_.find(stream_id); |
| |
| // A close request was received while translation was in progress |
| if (auth_data == authorizations_.end()) |
| return; |
| |
| if (!device_found) { |
| authorizations_.erase(auth_data); |
| Send(new AudioMsg_NotifyDeviceAuthorized(stream_id, false, DummyParams())); |
| return; |
| } |
| |
| auth_data->second.first = true; |
| auth_data->second.second = device_info.unique_id; |
| Send(new AudioMsg_NotifyDeviceAuthorized(stream_id, true, |
| device_info.output_params)); |
| } |
| |
| 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 << ")"; |
| |
| const auto& auth_data = authorizations_.find(stream_id); |
| |
| // If no previous authorization requested, assume default device |
| if (auth_data == authorizations_.end()) { |
| DoCreateStream(stream_id, render_frame_id, params, std::string()); |
| return; |
| } |
| |
| CHECK(auth_data->second.first); |
| DoCreateStream(stream_id, render_frame_id, params, auth_data->second.second); |
| authorizations_.erase(auth_data); |
| } |
| |
| void AudioRendererHost::DoCreateStream(int stream_id, |
| int render_frame_id, |
| const media::AudioParameters& params, |
| const std::string& device_unique_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // media::AudioParameters is validated in the deserializer. |
| if (LookupById(stream_id) != NULL) { |
| SendErrorMessage(stream_id); |
| return; |
| } |
| |
| // Create the shared memory and share with the renderer process. |
| uint32 shared_memory_size = AudioBus::CalculateMemorySize(params); |
| scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory()); |
| if (!shared_memory->CreateAndMapAnonymous(shared_memory_size)) { |
| SendErrorMessage(stream_id); |
| return; |
| } |
| |
| scoped_ptr<AudioSyncReader> reader( |
| new AudioSyncReader(shared_memory.get(), params)); |
| if (!reader->Init()) { |
| SendErrorMessage(stream_id); |
| return; |
| } |
| |
| MediaObserver* const media_observer = |
| GetContentClient()->browser()->GetMediaObserver(); |
| if (media_observer) |
| media_observer->OnCreatingAudioStream(render_process_id_, render_frame_id); |
| |
| scoped_ptr<AudioEntry> entry( |
| new AudioEntry(this, stream_id, render_frame_id, params, device_unique_id, |
| shared_memory.Pass(), reader.Pass())); |
| if (mirroring_manager_) { |
| mirroring_manager_->AddDiverter( |
| render_process_id_, entry->render_frame_id(), entry->controller()); |
| } |
| audio_entries_.insert(std::make_pair(stream_id, entry.release())); |
| |
| audio_log_->OnCreated(stream_id, params, device_unique_id); |
| MediaInternals::GetInstance()->SetWebContentsTitleForAudioLogEntry( |
| stream_id, render_process_id_, render_frame_id, audio_log_.get()); |
| } |
| |
| void AudioRendererHost::OnPlayStream(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| AudioEntry* entry = LookupById(stream_id); |
| if (!entry) { |
| SendErrorMessage(stream_id); |
| return; |
| } |
| |
| entry->controller()->Play(); |
| audio_log_->OnStarted(stream_id); |
| } |
| |
| void AudioRendererHost::OnPauseStream(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| AudioEntry* entry = LookupById(stream_id); |
| if (!entry) { |
| SendErrorMessage(stream_id); |
| return; |
| } |
| |
| entry->controller()->Pause(); |
| audio_log_->OnStopped(stream_id); |
| } |
| |
| void AudioRendererHost::OnSetVolume(int stream_id, double volume) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| AudioEntry* entry = LookupById(stream_id); |
| if (!entry) { |
| SendErrorMessage(stream_id); |
| return; |
| } |
| |
| // Make sure the volume is valid. |
| if (volume < 0 || volume > 1.0) |
| return; |
| entry->controller()->SetVolume(volume); |
| audio_log_->OnSetVolume(stream_id, volume); |
| } |
| |
| void AudioRendererHost::OnSwitchOutputDevice( |
| int stream_id, |
| int render_frame_id, |
| const std::string& device_id, |
| const url::Origin& security_origin) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DVLOG(1) << "AudioRendererHost@" << this |
| << "::OnSwitchOutputDevice(stream_id=" << stream_id |
| << ", render_frame_id=" << render_frame_id |
| << ", device_id=" << device_id |
| << ", security_origin=" << security_origin << ")"; |
| if (!LookupById(stream_id) || !IsValidDeviceId(device_id)) { |
| Send(new AudioMsg_NotifyOutputDeviceSwitched( |
| stream_id, media::SWITCH_OUTPUT_DEVICE_RESULT_ERROR_INTERNAL)); |
| return; |
| } |
| GURL gurl_security_origin = ConvertToGURL(security_origin); |
| CheckOutputDeviceAccess( |
| render_frame_id, device_id, gurl_security_origin, |
| base::Bind(&AudioRendererHost::OnSwitchDeviceAuthorized, this, stream_id, |
| device_id, gurl_security_origin)); |
| } |
| |
| void AudioRendererHost::OnSwitchDeviceAuthorized( |
| int stream_id, |
| const std::string& device_id, |
| const GURL& gurl_security_origin, |
| bool have_access) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!have_access) { |
| Send(new AudioMsg_NotifyOutputDeviceSwitched( |
| stream_id, media::SWITCH_OUTPUT_DEVICE_RESULT_ERROR_NOT_AUTHORIZED)); |
| return; |
| } |
| |
| AudioEntry* entry = LookupById(stream_id); |
| if (!entry) { |
| Send(new AudioMsg_NotifyOutputDeviceSwitched( |
| stream_id, media::SWITCH_OUTPUT_DEVICE_RESULT_ERROR_INTERNAL)); |
| return; |
| } |
| |
| entry->controller()->GetOutputDeviceId( |
| base::Bind(&AudioRendererHost::OnSwitchDeviceCurrentName, this, device_id, |
| gurl_security_origin, stream_id)); |
| } |
| |
| void AudioRendererHost::OnSwitchDeviceCurrentName( |
| const std::string& device_id, |
| const GURL& gurl_security_origin, |
| int stream_id, |
| const std::string& current_device_unique_id) { |
| media_stream_manager_->audio_output_device_enumerator()->Enumerate(base::Bind( |
| &AudioRendererHost::TranslateDeviceIDAndCheckParams, this, device_id, |
| gurl_security_origin, current_device_unique_id, |
| base::Bind(&AudioRendererHost::OnSwitchDeviceIDTranslatedAndParamsChecked, |
| this, stream_id))); |
| } |
| |
| void AudioRendererHost::OnSwitchDeviceIDTranslatedAndParamsChecked( |
| int stream_id, |
| bool success, |
| const AudioOutputDeviceInfo& device_info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!success) { |
| media::SwitchOutputDeviceResult result = |
| device_info.unique_id.empty() |
| ? media::SWITCH_OUTPUT_DEVICE_RESULT_ERROR_NOT_FOUND |
| : media::SWITCH_OUTPUT_DEVICE_RESULT_ERROR_INTERNAL; |
| |
| Send(new AudioMsg_NotifyOutputDeviceSwitched(stream_id, result)); |
| return; |
| } |
| |
| AudioEntry* entry = LookupById(stream_id); |
| if (!entry) { |
| Send(new AudioMsg_NotifyOutputDeviceSwitched( |
| stream_id, media::SWITCH_OUTPUT_DEVICE_RESULT_ERROR_INTERNAL)); |
| return; |
| } |
| |
| entry->controller()->SwitchOutputDevice( |
| device_info.unique_id, |
| base::Bind(&AudioRendererHost::OnDeviceSwitched, this, stream_id)); |
| audio_log_->OnSwitchOutputDevice(entry->stream_id(), device_info.unique_id); |
| } |
| |
| void AudioRendererHost::OnDeviceSwitched(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| Send(new AudioMsg_NotifyOutputDeviceSwitched( |
| stream_id, media::SWITCH_OUTPUT_DEVICE_RESULT_SUCCESS)); |
| } |
| |
| void AudioRendererHost::SendErrorMessage(int stream_id) { |
| Send(new AudioMsg_NotifyStreamStateChanged( |
| stream_id, media::AUDIO_OUTPUT_IPC_DELEGATE_STATE_ERROR)); |
| } |
| |
| void AudioRendererHost::OnCloseStream(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| authorizations_.erase(stream_id); |
| |
| // Prevent oustanding callbacks from attempting to close/delete the same |
| // AudioEntry twice. |
| AudioEntryMap::iterator i = audio_entries_.find(stream_id); |
| if (i == audio_entries_.end()) |
| return; |
| scoped_ptr<AudioEntry> entry(i->second); |
| audio_entries_.erase(i); |
| |
| media::AudioOutputController* const controller = entry->controller(); |
| controller->Close( |
| base::Bind(&AudioRendererHost::DeleteEntry, this, base::Passed(&entry))); |
| audio_log_->OnClosed(stream_id); |
| } |
| |
| void AudioRendererHost::DeleteEntry(scoped_ptr<AudioEntry> entry) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // De-register the controller from the AudioMirroringManager now that the |
| // controller has closed the AudioOutputStream and shut itself down. This |
| // ensures that calling RemoveDiverter() here won't trigger the controller to |
| // re-start the default AudioOutputStream and cause a brief audio blip to come |
| // out the user's speakers. http://crbug.com/474432 |
| if (mirroring_manager_) |
| mirroring_manager_->RemoveDiverter(entry->controller()); |
| |
| AudioStreamMonitor::StopMonitoringStream( |
| render_process_id_, entry->render_frame_id(), entry->stream_id()); |
| UpdateNumPlayingStreams(entry.get(), false); |
| } |
| |
| void AudioRendererHost::ReportErrorAndClose(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // Make sure this isn't a stray callback executing after the stream has been |
| // closed, so error notifications aren't sent after clients believe the stream |
| // is closed. |
| if (!LookupById(stream_id)) |
| return; |
| |
| SendErrorMessage(stream_id); |
| |
| audio_log_->OnError(stream_id); |
| OnCloseStream(stream_id); |
| } |
| |
| AudioRendererHost::AudioEntry* AudioRendererHost::LookupById(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| AudioEntryMap::const_iterator i = audio_entries_.find(stream_id); |
| return i != audio_entries_.end() ? i->second : NULL; |
| } |
| |
| void AudioRendererHost::UpdateNumPlayingStreams(AudioEntry* entry, |
| bool is_playing) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (entry->playing() == is_playing) |
| return; |
| |
| bool should_alert_resource_scheduler; |
| if (is_playing) { |
| should_alert_resource_scheduler = |
| !RenderFrameHasActiveAudio(entry->render_frame_id()); |
| entry->set_playing(true); |
| base::AtomicRefCountInc(&num_playing_streams_); |
| } else { |
| entry->set_playing(false); |
| should_alert_resource_scheduler = |
| !RenderFrameHasActiveAudio(entry->render_frame_id()); |
| base::AtomicRefCountDec(&num_playing_streams_); |
| } |
| |
| if (should_alert_resource_scheduler && ResourceDispatcherHostImpl::Get()) { |
| BrowserThread::PostTaskAndReplyWithResult( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&RenderFrameIdToRenderViewId, render_process_id_, |
| entry->render_frame_id()), |
| base::Bind(&NotifyResourceDispatcherOfAudioStateChange, |
| render_process_id_, is_playing)); |
| } |
| } |
| |
| bool AudioRendererHost::HasActiveAudio() { |
| return !base::AtomicRefCountIsZero(&num_playing_streams_); |
| } |
| |
| bool AudioRendererHost::RenderFrameHasActiveAudio(int render_frame_id) const { |
| for (AudioEntryMap::const_iterator it = audio_entries_.begin(); |
| it != audio_entries_.end(); |
| ++it) { |
| AudioEntry* entry = it->second; |
| if (entry->render_frame_id() == render_frame_id && entry->playing()) |
| return true; |
| } |
| return false; |
| } |
| |
| void AudioRendererHost::CheckOutputDeviceAccess( |
| int render_frame_id, |
| const std::string& device_id, |
| const GURL& gurl_security_origin, |
| const OutputDeviceAccessCB& callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // Skip origin check in requests for the default device with an empty |
| // security origin. |
| bool skip_origin_check = gurl_security_origin.is_empty() && device_id.empty(); |
| if (!skip_origin_check && |
| !ChildProcessSecurityPolicyImpl::GetInstance()->CanRequestURL( |
| render_process_id_, gurl_security_origin)) { |
| content::bad_message::ReceivedBadMessage(this, |
| bad_message::ARH_UNAUTHORIZED_URL); |
| return; |
| } |
| |
| if (device_id.empty()) { |
| callback.Run(true); |
| } else { |
| // Check that MediaStream device permissions have been granted, |
| // hence the use of a MediaStreamUIProxy. |
| scoped_ptr<MediaStreamUIProxy> ui_proxy = MediaStreamUIProxy::Create(); |
| |
| // Use MEDIA_DEVICE_AUDIO_CAPTURE instead of MEDIA_DEVICE_AUDIO_OUTPUT |
| // because MediaStreamUIProxy::CheckAccess does not currently support |
| // MEDIA_DEVICE_AUDIO_OUTPUT. |
| // TODO(guidou): Change to MEDIA_DEVICE_AUDIO_OUTPUT when support becomes |
| // available. http://crbug.com/498675 |
| ui_proxy->CheckAccess(gurl_security_origin, MEDIA_DEVICE_AUDIO_CAPTURE, |
| render_process_id_, render_frame_id, |
| base::Bind(&AudioRendererHost::AccessChecked, this, |
| base::Passed(&ui_proxy), callback)); |
| } |
| } |
| |
| void AudioRendererHost::AccessChecked(scoped_ptr<MediaStreamUIProxy> ui_proxy, |
| const OutputDeviceAccessCB& callback, |
| bool have_access) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| callback.Run(have_access); |
| } |
| |
| void AudioRendererHost::TranslateDeviceID( |
| const std::string& device_id, |
| const GURL& security_origin, |
| const OutputDeviceInfoCB& callback, |
| const AudioOutputDeviceEnumeration& device_infos) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| for (const AudioOutputDeviceInfo& device_info : device_infos) { |
| if (device_id.empty()) { |
| if (device_info.unique_id == media::AudioManagerBase::kDefaultDeviceId) { |
| callback.Run(true, device_info); |
| return; |
| } |
| } else if (content::DoesMediaDeviceIDMatchHMAC(salt_callback_, |
| security_origin, device_id, |
| device_info.unique_id)) { |
| callback.Run(true, device_info); |
| return; |
| } |
| } |
| DCHECK(!device_id.empty()); // Default device must always be found |
| AudioOutputDeviceInfo device_info = {std::string(), std::string(), |
| DummyParams()}; |
| callback.Run(false, device_info); |
| } |
| |
| void AudioRendererHost::TranslateDeviceIDAndCheckParams( |
| const std::string& device_id, |
| const GURL& gurl_security_origin, |
| const std::string& current_device_unique_id, |
| const OutputDeviceInfoCB& callback, |
| const AudioOutputDeviceEnumeration& device_infos) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| std::string current_unique_id = TranslateDefaultId(current_device_unique_id); |
| bool use_default_device = |
| device_id.empty() || |
| device_id == media::AudioManagerBase::kDefaultDeviceId; |
| AudioOutputDeviceEnumeration::const_iterator current_info = |
| device_infos.end(); |
| AudioOutputDeviceEnumeration::const_iterator new_info = device_infos.end(); |
| |
| for (AudioOutputDeviceEnumeration::const_iterator it = device_infos.begin(); |
| it != device_infos.end(); ++it) { |
| if (it->unique_id == current_unique_id) |
| current_info = it; |
| |
| if (use_default_device) { |
| if (it->unique_id == media::AudioManagerBase::kDefaultDeviceId) |
| new_info = it; |
| } else if (content::DoesMediaDeviceIDMatchHMAC(salt_callback_, |
| gurl_security_origin, |
| device_id, it->unique_id)) { |
| new_info = it; |
| } |
| |
| if (current_info != device_infos.end() && new_info != device_infos.end()) |
| break; |
| } |
| |
| // Use empty unique ID to indicate that |device_id| was not found |
| if (new_info == device_infos.end()) { |
| AudioOutputDeviceInfo dummy_info = {std::string(), std::string(), |
| DummyParams()}; |
| callback.Run(false, dummy_info); |
| return; |
| } |
| |
| bool success = current_info != device_infos.end() && |
| new_info->output_params.sample_rate() == |
| current_info->output_params.sample_rate() && |
| new_info->output_params.frames_per_buffer() == |
| current_info->output_params.frames_per_buffer() && |
| new_info->output_params.bits_per_sample() == |
| current_info->output_params.bits_per_sample(); |
| |
| callback.Run(success, *new_info); |
| } |
| |
| bool AudioRendererHost::IsAuthorizationStarted(int stream_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| const auto& i = authorizations_.find(stream_id); |
| return i != authorizations_.end(); |
| } |
| |
| } // namespace content |