blob: 478899c343a8dda61c25d2a1cb4593008640a073 [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 "base/bind.h"
#include "base/metrics/histogram.h"
#include "base/process.h"
#include "base/shared_memory.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/renderer_host/media/audio_sync_reader.h"
#include "content/common/media/audio_messages.h"
#include "content/public/browser/media_observer.h"
#include "media/audio/shared_memory_util.h"
#include "media/base/audio_bus.h"
#include "media/base/limits.h"
using content::BrowserMessageFilter;
using content::BrowserThread;
using media::AudioBus;
AudioRendererHost::AudioEntry::AudioEntry()
: stream_id(0),
pending_close(false) {
}
AudioRendererHost::AudioEntry::~AudioEntry() {}
///////////////////////////////////////////////////////////////////////////////
// AudioRendererHost implementations.
AudioRendererHost::AudioRendererHost(
media::AudioManager* audio_manager,
content::MediaObserver* media_observer)
: audio_manager_(audio_manager),
media_observer_(media_observer) {
}
AudioRendererHost::~AudioRendererHost() {
DCHECK(audio_entries_.empty());
}
void AudioRendererHost::OnChannelClosing() {
BrowserMessageFilter::OnChannelClosing();
// Since the IPC channel is gone, close all requested audio streams.
DeleteEntries();
}
void AudioRendererHost::OnDestruct() const {
BrowserThread::DeleteOnIOThread::Destruct(this);
}
///////////////////////////////////////////////////////////////////////////////
// media::AudioOutputController::EventHandler implementations.
void AudioRendererHost::OnCreated(media::AudioOutputController* controller) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(
&AudioRendererHost::DoCompleteCreation,
this,
make_scoped_refptr(controller)));
}
void AudioRendererHost::OnPlaying(media::AudioOutputController* controller) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(
&AudioRendererHost::DoSendPlayingMessage,
this,
make_scoped_refptr(controller)));
}
void AudioRendererHost::OnPaused(media::AudioOutputController* controller) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(
&AudioRendererHost::DoSendPausedMessage,
this,
make_scoped_refptr(controller)));
}
void AudioRendererHost::OnError(media::AudioOutputController* controller,
int error_code) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&AudioRendererHost::DoHandleError,
this, make_scoped_refptr(controller), error_code));
}
void AudioRendererHost::DoCompleteCreation(
media::AudioOutputController* controller) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupByController(controller);
if (!entry)
return;
if (!peer_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(peer_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;
}
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(peer_handle(),
&foreign_socket_handle)) {
DeleteEntryOnError(entry);
return;
}
Send(new AudioMsg_NotifyStreamCreated(
entry->stream_id,
foreign_memory_handle,
foreign_socket_handle,
media::PacketSizeInBytes(entry->shared_memory.created_size())));
}
void AudioRendererHost::DoSendPlayingMessage(
media::AudioOutputController* controller) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupByController(controller);
if (!entry)
return;
Send(new AudioMsg_NotifyStreamStateChanged(
entry->stream_id, media::AudioOutputIPCDelegate::kPlaying));
}
void AudioRendererHost::DoSendPausedMessage(
media::AudioOutputController* controller) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupByController(controller);
if (!entry)
return;
Send(new AudioMsg_NotifyStreamStateChanged(
entry->stream_id, media::AudioOutputIPCDelegate::kPaused));
}
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) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP_EX(AudioRendererHost, message, *message_was_ok)
IPC_MESSAGE_HANDLER(AudioHostMsg_CreateStream, OnCreateStream)
IPC_MESSAGE_HANDLER(AudioHostMsg_PlayStream, OnPlayStream)
IPC_MESSAGE_HANDLER(AudioHostMsg_PauseStream, OnPauseStream)
IPC_MESSAGE_HANDLER(AudioHostMsg_FlushStream, OnFlushStream)
IPC_MESSAGE_HANDLER(AudioHostMsg_CloseStream, OnCloseStream)
IPC_MESSAGE_HANDLER(AudioHostMsg_SetVolume, OnSetVolume)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP_EX()
return handled;
}
void AudioRendererHost::OnCreateStream(
int stream_id, const media::AudioParameters& params, int input_channels) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(LookupById(stream_id) == NULL);
media::AudioParameters audio_params(params);
uint32 buffer_size = media::AudioBus::CalculateMemorySize(audio_params);
DCHECK_GT(buffer_size, 0U);
DCHECK_LE(buffer_size,
static_cast<uint32>(media::limits::kMaxPacketSizeInBytes));
DCHECK_GE(input_channels, 0);
DCHECK_LT(input_channels, media::limits::kMaxChannels);
// Calculate output and input memory size.
int output_memory_size = AudioBus::CalculateMemorySize(audio_params);
DCHECK_GT(output_memory_size, 0);
int frames = audio_params.frames_per_buffer();
int input_memory_size =
AudioBus::CalculateMemorySize(input_channels, frames);
DCHECK_GE(input_memory_size, 0);
scoped_ptr<AudioEntry> entry(new AudioEntry());
// Create the shared memory and share with the renderer process.
// For synchronized I/O (if input_channels > 0) then we allocate
// extra memory after the output data for the input data.
uint32 io_buffer_size = output_memory_size + input_memory_size;
uint32 shared_memory_size =
media::TotalSharedMemorySizeInBytes(io_buffer_size);
if (!entry->shared_memory.CreateAndMapAnonymous(shared_memory_size)) {
// If creation of shared memory failed then send an error message.
SendErrorMessage(stream_id);
return;
}
// Create sync reader and try to initialize it.
scoped_ptr<AudioSyncReader> reader(
new AudioSyncReader(&entry->shared_memory, params, input_channels));
if (!reader->Init()) {
SendErrorMessage(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::Create(
audio_manager_, this, audio_params, entry->reader.get());
if (!entry->controller) {
SendErrorMessage(stream_id);
return;
}
// If we have created the controller successfully, create an entry and add it
// to the map.
entry->stream_id = stream_id;
audio_entries_.insert(std::make_pair(stream_id, entry.release()));
if (media_observer_)
media_observer_->OnSetAudioStreamStatus(this, stream_id, "created");
}
void AudioRendererHost::OnPlayStream(int stream_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupById(stream_id);
if (!entry) {
SendErrorMessage(stream_id);
return;
}
entry->controller->Play();
if (media_observer_)
media_observer_->OnSetAudioStreamPlaying(this, stream_id, true);
}
void AudioRendererHost::OnPauseStream(int stream_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupById(stream_id);
if (!entry) {
SendErrorMessage(stream_id);
return;
}
entry->controller->Pause();
if (media_observer_)
media_observer_->OnSetAudioStreamPlaying(this, stream_id, false);
}
void AudioRendererHost::OnFlushStream(int stream_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntry* entry = LookupById(stream_id);
if (!entry) {
SendErrorMessage(stream_id);
return;
}
entry->controller->Flush();
if (media_observer_)
media_observer_->OnSetAudioStreamStatus(this, stream_id, "flushed");
}
void AudioRendererHost::OnCloseStream(int stream_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (media_observer_)
media_observer_->OnSetAudioStreamStatus(this, stream_id, "closed");
AudioEntry* entry = LookupById(stream_id);
if (entry)
CloseAndDeleteStream(entry);
}
void AudioRendererHost::OnSetVolume(int stream_id, double volume) {
DCHECK(BrowserThread::CurrentlyOn(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);
if (media_observer_)
media_observer_->OnSetAudioStreamVolume(this, stream_id, volume);
}
void AudioRendererHost::SendErrorMessage(int32 stream_id) {
Send(new AudioMsg_NotifyStreamStateChanged(
stream_id, media::AudioOutputIPCDelegate::kError));
}
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) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!entry->pending_close) {
entry->controller->Close(
base::Bind(&AudioRendererHost::DeleteEntry, this, entry));
entry->pending_close = true;
}
}
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 identified by |stream_id| from the map.
audio_entries_.erase(entry->stream_id);
// Notify the media observer.
if (media_observer_)
media_observer_->OnDeleteAudioStream(this, 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->stream_id);
if (media_observer_)
media_observer_->OnSetAudioStreamStatus(this, entry->stream_id, "error");
CloseAndDeleteStream(entry);
}
AudioRendererHost::AudioEntry* AudioRendererHost::LookupById(int stream_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
AudioEntryMap::iterator i = audio_entries_.find(stream_id);
if (i != audio_entries_.end() && !i->second->pending_close)
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 (!i->second->pending_close && controller == i->second->controller.get())
return i->second;
}
return NULL;
}