blob: b18c4c3d1547a618426eccd39c8e63610bf44fc0 [file] [log] [blame]
// Copyright (c) 2010 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 "chrome/browser/renderer_host/audio_renderer_host.h"
#include "base/metrics/histogram.h"
#include "base/process.h"
#include "base/shared_memory.h"
#include "chrome/browser/renderer_host/audio_sync_reader.h"
#include "chrome/common/render_messages.h"
#include "chrome/common/render_messages_params.h"
#include "ipc/ipc_logging.h"
// The minimum number of samples in a hardware packet.
// This value is selected so that we can handle down to 5khz sample rate.
static const int kMinSamplesPerHardwarePacket = 1024;
// The maximum number of samples in a hardware packet.
// This value is selected so that we can handle up to 192khz sample rate.
static const int kMaxSamplesPerHardwarePacket = 64 * 1024;
// This constant governs the hardware audio buffer size, this value should be
// chosen carefully.
// This value is selected so that we have 8192 samples for 48khz streams.
static const int kMillisecondsPerHardwarePacket = 170;
static uint32 SelectSamplesPerPacket(AudioParameters params) {
// Select the number of samples that can provide at least
// |kMillisecondsPerHardwarePacket| worth of audio data.
int samples = kMinSamplesPerHardwarePacket;
while (samples <= kMaxSamplesPerHardwarePacket &&
samples * base::Time::kMillisecondsPerSecond <
params.sample_rate * kMillisecondsPerHardwarePacket) {
samples *= 2;
}
return samples;
}
AudioRendererHost::AudioEntry::AudioEntry()
: render_view_id(0),
stream_id(0),
pending_buffer_request(false),
pending_close(false) {
}
AudioRendererHost::AudioEntry::~AudioEntry() {}
///////////////////////////////////////////////////////////////////////////////
// AudioRendererHost implementations.
AudioRendererHost::AudioRendererHost()
: process_id_(0),
process_handle_(0),
ipc_sender_(NULL) {
// Increase the ref count of this object so it is active until we do
// Release().
AddRef();
}
AudioRendererHost::~AudioRendererHost() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(audio_entries_.empty());
// Make sure we received IPCChannelClosing() signal.
DCHECK(!ipc_sender_);
DCHECK(!process_handle_);
}
void AudioRendererHost::Destroy() {
// Post a message to the thread where this object should live and do the
// actual operations there.
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this, &AudioRendererHost::DoDestroy));
}
// Event received when IPC channel is connected to the renderer process.
void AudioRendererHost::IPCChannelConnected(int process_id,
base::ProcessHandle process_handle,
IPC::Message::Sender* ipc_sender) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
process_handle_ = process_handle;
ipc_sender_ = ipc_sender;
}
// Event received when IPC channel is closing.
void AudioRendererHost::IPCChannelClosing() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Reset IPC related member variables.
ipc_sender_ = NULL;
process_handle_ = 0;
// Since the IPC channel is gone, close all requested audio streams.
DeleteEntries();
}
///////////////////////////////////////////////////////////////////////////////
// media::AudioOutputController::EventHandler implementations.
void AudioRendererHost::OnCreated(media::AudioOutputController* controller) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(
this,
&AudioRendererHost::DoCompleteCreation,
make_scoped_refptr(controller)));
}
void AudioRendererHost::OnPlaying(media::AudioOutputController* controller) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(
this,
&AudioRendererHost::DoSendPlayingMessage,
make_scoped_refptr(controller)));
}
void AudioRendererHost::OnPaused(media::AudioOutputController* controller) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(
this,
&AudioRendererHost::DoSendPausedMessage,
make_scoped_refptr(controller)));
}
void AudioRendererHost::OnError(media::AudioOutputController* controller,
int error_code) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(this,
&AudioRendererHost::DoHandleError,
make_scoped_refptr(controller),
error_code));
}
void AudioRendererHost::OnMoreData(media::AudioOutputController* controller,
AudioBuffersState buffers_state) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(this,
&AudioRendererHost::DoRequestMoreData,
make_scoped_refptr(controller),
buffers_state));
}
void AudioRendererHost::DoCompleteCreation(
media::AudioOutputController* controller) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupByController(controller);
if (!entry)
return;
if (!process_handle_) {
NOTREACHED() << "Renderer process handle is invalid.";
DeleteEntryOnError(entry);
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(process_handle_,
&foreign_memory_handle)) {
// If we failed to map and share the shared memory then close the audio
// stream and send an error message.
DeleteEntryOnError(entry);
return;
}
if (entry->controller->LowLatencyMode()) {
AudioSyncReader* reader =
static_cast<AudioSyncReader*>(entry->reader.get());
#if defined(OS_WIN)
base::SyncSocket::Handle foreign_socket_handle;
#else
base::FileDescriptor foreign_socket_handle;
#endif
// If we failed to prepare the sync socket for the renderer then we fail
// the construction of audio stream.
if (!reader->PrepareForeignSocketHandle(process_handle_,
&foreign_socket_handle)) {
DeleteEntryOnError(entry);
return;
}
SendMessage(new ViewMsg_NotifyLowLatencyAudioStreamCreated(
entry->render_view_id, entry->stream_id, foreign_memory_handle,
foreign_socket_handle, entry->shared_memory.created_size()));
return;
}
// The normal audio stream has created, send a message to the renderer
// process.
SendMessage(new ViewMsg_NotifyAudioStreamCreated(
entry->render_view_id, entry->stream_id, foreign_memory_handle,
entry->shared_memory.created_size()));
}
void AudioRendererHost::DoSendPlayingMessage(
media::AudioOutputController* controller) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupByController(controller);
if (!entry)
return;
ViewMsg_AudioStreamState_Params params;
params.state = ViewMsg_AudioStreamState_Params::kPlaying;
SendMessage(new ViewMsg_NotifyAudioStreamStateChanged(
entry->render_view_id, entry->stream_id, params));
}
void AudioRendererHost::DoSendPausedMessage(
media::AudioOutputController* controller) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupByController(controller);
if (!entry)
return;
ViewMsg_AudioStreamState_Params params;
params.state = ViewMsg_AudioStreamState_Params::kPaused;
SendMessage(new ViewMsg_NotifyAudioStreamStateChanged(
entry->render_view_id, entry->stream_id, params));
}
void AudioRendererHost::DoRequestMoreData(
media::AudioOutputController* controller,
AudioBuffersState buffers_state) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// If we already have a pending request then return.
AudioEntry* entry = LookupByController(controller);
if (!entry || entry->pending_buffer_request)
return;
DCHECK(!entry->controller->LowLatencyMode());
entry->pending_buffer_request = true;
SendMessage(
new ViewMsg_RequestAudioPacket(
entry->render_view_id, entry->stream_id, buffers_state));
}
void AudioRendererHost::DoHandleError(media::AudioOutputController* controller,
int error_code) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupByController(controller);
if (!entry)
return;
DeleteEntryOnError(entry);
}
///////////////////////////////////////////////////////////////////////////////
// IPC Messages handler
bool AudioRendererHost::OnMessageReceived(const IPC::Message& message,
bool* message_was_ok) {
if (!IsAudioRendererHostMessage(message))
return false;
*message_was_ok = true;
IPC_BEGIN_MESSAGE_MAP_EX(AudioRendererHost, message, *message_was_ok)
IPC_MESSAGE_HANDLER(ViewHostMsg_CreateAudioStream, OnCreateStream)
IPC_MESSAGE_HANDLER(ViewHostMsg_PlayAudioStream, OnPlayStream)
IPC_MESSAGE_HANDLER(ViewHostMsg_PauseAudioStream, OnPauseStream)
IPC_MESSAGE_HANDLER(ViewHostMsg_FlushAudioStream, OnFlushStream)
IPC_MESSAGE_HANDLER(ViewHostMsg_CloseAudioStream, OnCloseStream)
IPC_MESSAGE_HANDLER(ViewHostMsg_NotifyAudioPacketReady, OnNotifyPacketReady)
IPC_MESSAGE_HANDLER(ViewHostMsg_GetAudioVolume, OnGetVolume)
IPC_MESSAGE_HANDLER(ViewHostMsg_SetAudioVolume, OnSetVolume)
IPC_END_MESSAGE_MAP_EX()
return true;
}
bool AudioRendererHost::IsAudioRendererHostMessage(
const IPC::Message& message) {
switch (message.type()) {
case ViewHostMsg_CreateAudioStream::ID:
case ViewHostMsg_PlayAudioStream::ID:
case ViewHostMsg_PauseAudioStream::ID:
case ViewHostMsg_FlushAudioStream::ID:
case ViewHostMsg_CloseAudioStream::ID:
case ViewHostMsg_NotifyAudioPacketReady::ID:
case ViewHostMsg_GetAudioVolume::ID:
case ViewHostMsg_SetAudioVolume::ID:
return true;
default:
break;
}
return false;
}
void AudioRendererHost::OnCreateStream(
const IPC::Message& msg, int stream_id,
const ViewHostMsg_Audio_CreateStream_Params& params, bool low_latency) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(LookupById(msg.routing_id(), stream_id) == NULL);
AudioParameters audio_params(params.params);
// Select the hardware packet size if not specified.
if (!audio_params.samples_per_packet) {
audio_params.samples_per_packet = SelectSamplesPerPacket(audio_params);
}
uint32 packet_size = audio_params.GetPacketSize();
scoped_ptr<AudioEntry> entry(new AudioEntry());
// Create the shared memory and share with the renderer process.
if (!entry->shared_memory.CreateAndMapAnonymous(packet_size)) {
// If creation of shared memory failed then send an error message.
SendErrorMessage(msg.routing_id(), stream_id);
return;
}
if (low_latency) {
// If this is the low latency mode, we need to construct a SyncReader first.
scoped_ptr<AudioSyncReader> reader(
new AudioSyncReader(&entry->shared_memory));
// Then try to initialize the sync reader.
if (!reader->Init()) {
SendErrorMessage(msg.routing_id(), stream_id);
return;
}
// If we have successfully created the SyncReader then assign it to the
// entry and construct an AudioOutputController.
entry->reader.reset(reader.release());
entry->controller =
media::AudioOutputController::CreateLowLatency(this, audio_params,
entry->reader.get());
} else {
// The choice of buffer capacity is based on experiment.
entry->controller =
media::AudioOutputController::Create(this, audio_params,
3 * packet_size);
}
if (!entry->controller) {
SendErrorMessage(msg.routing_id(), stream_id);
return;
}
// If we have created the controller successfully create a entry and add it
// to the map.
entry->render_view_id = msg.routing_id();
entry->stream_id = stream_id;
audio_entries_.insert(std::make_pair(
AudioEntryId(msg.routing_id(), stream_id),
entry.release()));
}
void AudioRendererHost::OnPlayStream(const IPC::Message& msg, int stream_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupById(msg.routing_id(), stream_id);
if (!entry) {
SendErrorMessage(msg.routing_id(), stream_id);
return;
}
entry->controller->Play();
}
void AudioRendererHost::OnPauseStream(const IPC::Message& msg, int stream_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupById(msg.routing_id(), stream_id);
if (!entry) {
SendErrorMessage(msg.routing_id(), stream_id);
return;
}
entry->controller->Pause();
}
void AudioRendererHost::OnFlushStream(const IPC::Message& msg, int stream_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupById(msg.routing_id(), stream_id);
if (!entry) {
SendErrorMessage(msg.routing_id(), stream_id);
return;
}
entry->controller->Flush();
}
void AudioRendererHost::OnCloseStream(const IPC::Message& msg, int stream_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupById(msg.routing_id(), stream_id);
if (entry)
CloseAndDeleteStream(entry);
}
void AudioRendererHost::OnSetVolume(const IPC::Message& msg, int stream_id,
double volume) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupById(msg.routing_id(), stream_id);
if (!entry) {
SendErrorMessage(msg.routing_id(), stream_id);
return;
}
// Make sure the volume is valid.
CHECK(volume >= 0 && volume <= 1.0);
entry->controller->SetVolume(volume);
}
void AudioRendererHost::OnGetVolume(const IPC::Message& msg, int stream_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
NOTREACHED() << "This message shouldn't be received";
}
void AudioRendererHost::OnNotifyPacketReady(
const IPC::Message& msg, int stream_id, uint32 packet_size) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupById(msg.routing_id(), stream_id);
if (!entry) {
SendErrorMessage(msg.routing_id(), stream_id);
return;
}
DCHECK(!entry->controller->LowLatencyMode());
CHECK(packet_size <= entry->shared_memory.created_size());
if (!entry->pending_buffer_request) {
NOTREACHED() << "Buffer received but no such pending request";
}
entry->pending_buffer_request = false;
// Enqueue the data to media::AudioOutputController.
entry->controller->EnqueueData(
reinterpret_cast<uint8*>(entry->shared_memory.memory()),
packet_size);
}
void AudioRendererHost::DoDestroy() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Reset IPC releated members.
ipc_sender_ = NULL;
process_handle_ = 0;
// Close all audio streams.
DeleteEntries();
// Decrease the reference to this object, which may lead to self-destruction.
Release();
}
void AudioRendererHost::SendMessage(IPC::Message* message) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (ipc_sender_)
ipc_sender_->Send(message);
}
void AudioRendererHost::SendErrorMessage(int32 render_view_id,
int32 stream_id) {
ViewMsg_AudioStreamState_Params state;
state.state = ViewMsg_AudioStreamState_Params::kError;
SendMessage(new ViewMsg_NotifyAudioStreamStateChanged(
render_view_id, stream_id, state));
}
void AudioRendererHost::DeleteEntries() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
for (AudioEntryMap::iterator i = audio_entries_.begin();
i != audio_entries_.end(); ++i) {
CloseAndDeleteStream(i->second);
}
}
void AudioRendererHost::CloseAndDeleteStream(AudioEntry* entry) {
if (!entry->pending_close) {
entry->controller->Close(
NewRunnableMethod(this, &AudioRendererHost::OnStreamClosed, entry));
entry->pending_close = true;
}
}
void AudioRendererHost::OnStreamClosed(AudioEntry* entry) {
// Delete the entry after we've closed the stream.
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this, &AudioRendererHost::DeleteEntry, entry));
}
void AudioRendererHost::DeleteEntry(AudioEntry* entry) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Delete the entry when this method goes out of scope.
scoped_ptr<AudioEntry> entry_deleter(entry);
// Erase the entry from the map.
audio_entries_.erase(
AudioEntryId(entry->render_view_id, entry->stream_id));
}
void AudioRendererHost::DeleteEntryOnError(AudioEntry* entry) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Sends the error message first before we close the stream because
// |entry| is destroyed in DeleteEntry().
SendErrorMessage(entry->render_view_id, entry->stream_id);
CloseAndDeleteStream(entry);
}
AudioRendererHost::AudioEntry* AudioRendererHost::LookupById(
int route_id, int stream_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntryMap::iterator i = audio_entries_.find(
AudioEntryId(route_id, stream_id));
if (i != audio_entries_.end())
return i->second;
return NULL;
}
AudioRendererHost::AudioEntry* AudioRendererHost::LookupByController(
media::AudioOutputController* controller) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Iterate the map of entries.
// TODO(hclam): Implement a faster look up method.
for (AudioEntryMap::iterator i = audio_entries_.begin();
i != audio_entries_.end(); ++i) {
if (controller == i->second->controller.get())
return i->second;
}
return NULL;
}