diff options
Diffstat (limited to 'content/browser/renderer_host/media/audio_input_renderer_host.cc')
-rw-r--r-- | content/browser/renderer_host/media/audio_input_renderer_host.cc | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/content/browser/renderer_host/media/audio_input_renderer_host.cc b/content/browser/renderer_host/media/audio_input_renderer_host.cc new file mode 100644 index 0000000..4a5c57a --- /dev/null +++ b/content/browser/renderer_host/media/audio_input_renderer_host.cc @@ -0,0 +1,376 @@ +// Copyright (c) 2011 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_input_renderer_host.h" + +#include "base/metrics/histogram.h" +#include "base/process.h" +#include "base/shared_memory.h" +#include "content/browser/renderer_host/media/audio_common.h" +#include "content/browser/renderer_host/media/audio_input_sync_writer.h" +#include "content/common/audio_messages.h" +#include "ipc/ipc_logging.h" + + +AudioInputRendererHost::AudioEntry::AudioEntry() + : render_view_id(0), + stream_id(0), + pending_close(false) { +} + +AudioInputRendererHost::AudioEntry::~AudioEntry() {} + +AudioInputRendererHost::AudioInputRendererHost() {} + +AudioInputRendererHost::~AudioInputRendererHost() { + DCHECK(audio_entries_.empty()); +} + +void AudioInputRendererHost::OnChannelClosing() { + BrowserMessageFilter::OnChannelClosing(); + + // Since the IPC channel is gone, close all requested audio streams. + DeleteEntries(); +} + +void AudioInputRendererHost::OnDestruct() const { + BrowserThread::DeleteOnIOThread::Destruct(this); +} + +void AudioInputRendererHost::OnCreated( + media::AudioInputController* controller) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + NewRunnableMethod( + this, + &AudioInputRendererHost::DoCompleteCreation, + make_scoped_refptr(controller))); +} + +void AudioInputRendererHost::OnRecording( + media::AudioInputController* controller) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + NewRunnableMethod( + this, + &AudioInputRendererHost::DoSendRecordingMessage, + make_scoped_refptr(controller))); +} + +void AudioInputRendererHost::OnError( + media::AudioInputController* controller, + int error_code) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + NewRunnableMethod(this, + &AudioInputRendererHost::DoHandleError, + make_scoped_refptr(controller), + error_code)); +} + +void AudioInputRendererHost::OnData(media::AudioInputController* controller, + const uint8* data, + uint32 size) { + NOTREACHED() << "Only low-latency mode is supported."; +} + +void AudioInputRendererHost::DoCompleteCreation( + media::AudioInputController* controller) { + VLOG(1) << "AudioInputRendererHost::DoCompleteCreation()"; + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; + + if (!peer_handle()) { + NOTREACHED() << "Renderer process handle is invalid."; + DeleteEntryOnError(entry); + return; + } + + if (!entry->controller->LowLatencyMode()) { + NOTREACHED() << "Only low-latency mode is supported."; + 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; + } + + if (entry->controller->LowLatencyMode()) { + AudioInputSyncWriter* writer = + static_cast<AudioInputSyncWriter*>(entry->writer.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 input stream. + if (!writer->PrepareForeignSocketHandle(peer_handle(), + &foreign_socket_handle)) { + DeleteEntryOnError(entry); + return; + } + + Send(new AudioInputMsg_NotifyLowLatencyStreamCreated( + entry->render_view_id, entry->stream_id, foreign_memory_handle, + foreign_socket_handle, entry->shared_memory.created_size())); + return; + } +} + +void AudioInputRendererHost::DoSendRecordingMessage( + media::AudioInputController* controller) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // TODO(henrika): TBI? + NOTIMPLEMENTED(); +} + +void AudioInputRendererHost::DoSendPausedMessage( + media::AudioInputController* controller) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // TODO(henrika): TBI? + NOTREACHED(); +} + +void AudioInputRendererHost::DoHandleError( + media::AudioInputController* controller, + int error_code) { + DLOG(WARNING) << "AudioInputRendererHost::DoHandleError(error_code=" + << error_code << ")"; + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; + + DeleteEntryOnError(entry); +} + +bool AudioInputRendererHost::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(AudioInputRendererHost, message, *message_was_ok) + IPC_MESSAGE_HANDLER(AudioInputHostMsg_CreateStream, OnCreateStream) + IPC_MESSAGE_HANDLER(AudioInputHostMsg_RecordStream, OnRecordStream) + IPC_MESSAGE_HANDLER(AudioInputHostMsg_CloseStream, OnCloseStream) + IPC_MESSAGE_HANDLER(AudioInputHostMsg_GetVolume, OnGetVolume) + IPC_MESSAGE_HANDLER(AudioInputHostMsg_SetVolume, OnSetVolume) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + + return handled; +} + +void AudioInputRendererHost::OnCreateStream( + const IPC::Message& msg, int stream_id, + const AudioParameters& params, bool low_latency) { + VLOG(1) << "AudioInputRendererHost::OnCreateStream(stream_id=" + << stream_id << ")"; + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(LookupById(msg.routing_id(), stream_id) == NULL); + + // Prevent the renderer process from asking for a normal-latency + // input stream. + if (!low_latency) { + NOTREACHED() << "Current implementation only supports low-latency mode."; + return; + } + + AudioParameters audio_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; + } + + // This is a low latency mode, hence we need to construct a SyncWriter first. + scoped_ptr<AudioInputSyncWriter> writer( + new AudioInputSyncWriter(&entry->shared_memory)); + + // Then try to initialize the sync writer. + if (!writer->Init()) { + SendErrorMessage(msg.routing_id(), stream_id); + return; + } + + // If we have successfully created the SyncWriter then assign it to the + // entry and construct an AudioInputController. + entry->writer.reset(writer.release()); + entry->controller = + media::AudioInputController::CreateLowLatency(this, + audio_params, + entry->writer.get()); + + 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 AudioInputRendererHost::OnRecordStream(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->Record(); +} + +void AudioInputRendererHost::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 AudioInputRendererHost::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; + } + + // TODO(henrika): TBI. + NOTIMPLEMENTED(); +} + +void AudioInputRendererHost::OnGetVolume(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; + } + + // TODO(henrika): TBI. + NOTIMPLEMENTED(); +} + +void AudioInputRendererHost::SendErrorMessage(int32 render_view_id, + int32 stream_id) { + // TODO(henrika): error state for audio input is not unique + Send(new AudioMsg_NotifyStreamStateChanged(render_view_id, + stream_id, + kAudioStreamError)); +} + +void AudioInputRendererHost::DeleteEntries() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + for (AudioEntryMap::iterator i = audio_entries_.begin(); + i != audio_entries_.end(); ++i) { + CloseAndDeleteStream(i->second); + } +} + +void AudioInputRendererHost::CloseAndDeleteStream(AudioEntry* entry) { + if (!entry->pending_close) { + entry->pending_close = true; + // TODO(henrika): AudioRendererHost uses an alternative method + // to close down the AudioController. Try to refactor and merge + // the implementations. + entry->controller->Close(); + OnStreamClosed(entry); + } +} + +void AudioInputRendererHost::OnStreamClosed(AudioEntry* entry) { + // Delete the entry after we've closed the stream. + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &AudioInputRendererHost::DeleteEntry, entry)); +} + +void AudioInputRendererHost::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 AudioInputRendererHost::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); +} + +AudioInputRendererHost::AudioEntry* AudioInputRendererHost::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; +} + +AudioInputRendererHost::AudioEntry* AudioInputRendererHost::LookupByController( + media::AudioInputController* 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; +} |