blob: 6a99670a793ba144b237cd33b5fbc907f79cf519 [file] [log] [blame]
// 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