diff options
Diffstat (limited to 'chrome/browser/renderer_host/audio_renderer_host.cc')
-rw-r--r-- | chrome/browser/renderer_host/audio_renderer_host.cc | 801 |
1 files changed, 341 insertions, 460 deletions
diff --git a/chrome/browser/renderer_host/audio_renderer_host.cc b/chrome/browser/renderer_host/audio_renderer_host.cc index 4d3226c..b714723 100644 --- a/chrome/browser/renderer_host/audio_renderer_host.cc +++ b/chrome/browser/renderer_host/audio_renderer_host.cc @@ -1,24 +1,6 @@ // 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. -// -// TODO(hclam): Several changes need to be made to this code: -// 1. We should host AudioRendererHost on a dedicated audio thread. Doing -// so we don't have to worry about blocking method calls such as -// play / stop an audio stream. -// 2. Move locked data structures into a separate structure that sanity -// checks access by different threads that use it. -// -// SEMANTICS OF |state_| -// Note that |state_| of IPCAudioSource is accessed on two thread. Namely -// the IO thread and the audio thread. IO thread is the thread on which -// IPAudioSource::Play(), IPCAudioSource::Pause() are called. Audio thread -// is a thread operated by the audio hardware for requesting data. -// It is important that |state_| is only written on the IO thread because -// reading of such state in Play() and Pause() is not protected. However, -// since OnMoreData() is called on the audio thread and reads |state_| -// variable. Writing to this variable needs to be protected in Play() -// and Pause(). #include "chrome/browser/renderer_host/audio_renderer_host.h" @@ -28,6 +10,7 @@ #include "base/shared_memory.h" #include "base/sys_info.h" #include "base/waitable_event.h" +#include "chrome/browser/renderer_host/audio_sync_reader.h" #include "chrome/common/render_messages.h" #include "ipc/ipc_logging.h" @@ -44,15 +27,6 @@ static const int kMaxSamplesPerHardwarePacket = 64 * 1024; // This value is selected so that we have 8192 samples for 48khz streams. static const int kMillisecondsPerHardwarePacket = 170; -// The following parameters limit the request buffer and packet size from the -// renderer to avoid renderer from requesting too much memory. -static const uint32 kMegabytes = 1024 * 1024; -static const uint32 kMaxDecodedPacketSize = 2 * kMegabytes; -static const uint32 kMaxBufferCapacity = 5 * kMegabytes; -static const int kMaxChannels = 32; -static const int kMaxBitsPerSample = 64; -static const int kMaxSampleRate = 192000; - // We allow at most 50 concurrent audio streams in most case. This is a // rather high limit that is practically hard to reach. static const size_t kMaxStreams = 50; @@ -94,389 +68,211 @@ static uint32 SelectHardwarePacketSize(int channels, int sample_rate, return channels * samples * bits_per_sample / 8; } -//----------------------------------------------------------------------------- -// AudioRendererHost::IPCAudioSource implementations. - -AudioRendererHost::IPCAudioSource::IPCAudioSource( - AudioRendererHost* host, - int process_id, - int route_id, - int stream_id, - AudioOutputStream* stream, - uint32 hardware_packet_size, - uint32 buffer_capacity) - : host_(host), - process_id_(process_id), - route_id_(route_id), - stream_id_(stream_id), - stream_(stream), - hardware_packet_size_(hardware_packet_size), - buffer_capacity_(buffer_capacity), - state_(kCreated), - outstanding_request_(false), - pending_bytes_(0) { -} - -AudioRendererHost::IPCAudioSource::~IPCAudioSource() { - DCHECK(kClosed == state_ || kCreated == state_); -} - -// static -AudioRendererHost::IPCAudioSource* -AudioRendererHost::IPCAudioSource::CreateIPCAudioSource( - AudioRendererHost* host, - int process_id, - int route_id, - int stream_id, - base::ProcessHandle process_handle, - AudioManager::Format format, - int channels, - int sample_rate, - char bits_per_sample, - uint32 packet_size, - bool low_latency) { - // Perform come preliminary checks on the parameters. - if (channels <= 0 || channels > kMaxChannels) - return NULL; - - if (sample_rate <= 0 || sample_rate > kMaxSampleRate) - return NULL; - - if (bits_per_sample <= 0 || bits_per_sample > kMaxBitsPerSample) - return NULL; - - // Create the stream in the first place. - AudioOutputStream* stream = - AudioManager::GetAudioManager()->MakeAudioStream( - format, channels, sample_rate, bits_per_sample); - - uint32 hardware_packet_size = packet_size; - - // If the packet size is not specified in the message we generate the best - // value here. - if (!hardware_packet_size) { - hardware_packet_size = - SelectHardwarePacketSize(channels, sample_rate, bits_per_sample); - } - uint32 transport_packet_size = hardware_packet_size; - - // We set the buffer capacity to be more than the total buffer amount in the - // audio hardware. This gives about 500ms of buffer which is a safe amount - // to avoid "audio clicks". - uint32 buffer_capacity = 3 * hardware_packet_size; - - if (stream && !stream->Open(hardware_packet_size)) { - stream->Close(); - stream = NULL; - } - - if (stream) { - IPCAudioSource* source = new IPCAudioSource( - host, - process_id, - route_id, - stream_id, - stream, - hardware_packet_size, - buffer_capacity); - // If we can open the stream, proceed with sharing the shared memory. - base::SharedMemoryHandle foreign_memory_handle; - - // Time to create the PCM transport. Either low latency or regular latency - // If things go well we send a message back to the renderer with the - // transport information. - // Note that the low latency mode is not yet ready and the if part of this - // method is never executed. TODO(cpu): Enable this mode. - - if (source->shared_memory_.Create(L"", false, false, - transport_packet_size) && - source->shared_memory_.Map(transport_packet_size) && - source->shared_memory_.ShareToProcess(process_handle, - &foreign_memory_handle)) { - if (low_latency) { - // Low latency mode. We use SyncSocket to signal. - base::SyncSocket* sockets[2] = {0}; - if (base::SyncSocket::CreatePair(sockets)) { - source->shared_socket_.reset(sockets[0]); -#if defined(OS_WIN) - HANDLE foreign_socket_handle = 0; - ::DuplicateHandle(GetCurrentProcess(), sockets[1]->handle(), - process_handle, &foreign_socket_handle, - 0, FALSE, DUPLICATE_SAME_ACCESS); - bool valid = foreign_socket_handle != 0; -#else - base::FileDescriptor foreign_socket_handle(sockets[1]->handle(), - false); - bool valid = foreign_socket_handle.fd != -1; -#endif - - if (valid) { - host->Send(new ViewMsg_NotifyLowLatencyAudioStreamCreated( - route_id, stream_id, foreign_memory_handle, - foreign_socket_handle, transport_packet_size)); - return source; - } - } - } else { - // Regular latency mode. - host->Send(new ViewMsg_NotifyAudioStreamCreated( - route_id, stream_id, foreign_memory_handle, - transport_packet_size)); - - // Also request the first packet to kick start the pre-rolling. - source->StartBuffering(); - return source; - } - } - // Failure. Close and free acquired resources. - source->Close(); - delete source; - } - - host->SendErrorMessage(route_id, stream_id); - return NULL; +/////////////////////////////////////////////////////////////////////////////// +// AudioRendererHost implementations. +AudioRendererHost::AudioRendererHost() + : process_handle_(0), + ipc_sender_(NULL) { + // Increase the ref count of this object so it is active until we do + // Release(). + AddRef(); } -void AudioRendererHost::IPCAudioSource::Play() { - // We can start from created or paused state. - if (!stream_ || (state_ != kCreated && state_ != kPaused)) - return; - - ViewMsg_AudioStreamState_Params state; - state.state = ViewMsg_AudioStreamState_Params::kPlaying; - host_->Send(new ViewMsg_NotifyAudioStreamStateChanged( - route_id_, stream_id_, state)); - - State old_state; - // Update the state and notify renderer. - { - AutoLock auto_lock(lock_); - old_state = state_; - state_ = kPlaying; - } +AudioRendererHost::~AudioRendererHost() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + DCHECK(audio_entries_.empty()); - if (old_state == kCreated) - stream_->Start(this); + // Make sure we received IPCChannelClosing() signal. + DCHECK(!ipc_sender_); + DCHECK(!process_handle_); } -void AudioRendererHost::IPCAudioSource::Pause() { - // We can pause from started state. - if (state_ != kPlaying) - return; - - // Update the state and notify renderer. - { - AutoLock auto_lock(lock_); - state_ = kPaused; - } - - ViewMsg_AudioStreamState_Params state; - state.state = ViewMsg_AudioStreamState_Params::kPaused; - host_->Send(new ViewMsg_NotifyAudioStreamStateChanged( - route_id_, stream_id_, state)); +void AudioRendererHost::Destroy() { + // Post a message to the thread where this object should live and do the + // actual operations there. + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod(this, &AudioRendererHost::DoDestroy)); } -void AudioRendererHost::IPCAudioSource::Flush() { - if (state_ != kPaused) - return; +// 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(ChromeThread::CurrentlyOn(ChromeThread::IO)); - AutoLock auto_lock(lock_); - push_source_.ClearAll(); + process_handle_ = process_handle; + ipc_sender_ = ipc_sender; } -void AudioRendererHost::IPCAudioSource::Close() { - if (!stream_) - return; +// Event received when IPC channel is closing. +void AudioRendererHost::IPCChannelClosing() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - stream_->Stop(); - stream_->Close(); - // After stream is closed it is destroyed, so don't keep a reference to it. - stream_ = NULL; + // Reset IPC related member variables. + ipc_sender_ = NULL; + process_handle_ = 0; - // Update the current state. - state_ = kClosed; + // Since the IPC channel is gone, close all requested audio streams. + DeleteEntries(); } -void AudioRendererHost::IPCAudioSource::SetVolume(double volume) { - // TODO(hclam): maybe send an error message back to renderer if this object - // is in a wrong state. - if (!stream_) - return; - stream_->SetVolume(volume); +/////////////////////////////////////////////////////////////////////////////// +// media::AudioController::EventHandler implementations. +void AudioRendererHost::OnCreated(media::AudioController* controller) { + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod(this, &AudioRendererHost::DoCompleteCreation, + controller)); } -void AudioRendererHost::IPCAudioSource::GetVolume() { - // TODO(hclam): maybe send an error message back to renderer if this object - // is in a wrong state. - if (!stream_) - return; - double volume; - stream_->GetVolume(&volume); - host_->Send(new ViewMsg_NotifyAudioStreamVolume(route_id_, stream_id_, - volume)); +void AudioRendererHost::OnPlaying(media::AudioController* controller) { + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod(this, &AudioRendererHost::DoSendPlayingMessage, + controller)); } -uint32 AudioRendererHost::IPCAudioSource::OnMoreData(AudioOutputStream* stream, - void* dest, - uint32 max_size, - uint32 pending_bytes) { - AutoLock auto_lock(lock_); - - // Record the callback time. - last_callback_time_ = base::Time::Now(); - - if (state_ != kPlaying) { - // Don't read anything. Save the number of bytes in the hardware buffer. - pending_bytes_ = pending_bytes; - return 0; - } - - uint32 size; - if (!shared_socket_.get()) { - // Push source doesn't need to know the stream and number of pending bytes. - // So just pass in NULL and 0 for them. - size = push_source_.OnMoreData(NULL, dest, max_size, 0); - pending_bytes_ = pending_bytes + size; - SubmitPacketRequest(&auto_lock); - } else { - // Low latency mode. - size = std::min(shared_memory_.max_size(), max_size); - memcpy(dest, shared_memory_.memory(), size); - memset(shared_memory_.memory(), 0, shared_memory_.max_size()); - shared_socket_->Send(&pending_bytes, sizeof(pending_bytes)); - } - - return size; +void AudioRendererHost::OnPaused(media::AudioController* controller) { + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod(this, &AudioRendererHost::DoSendPausedMessage, + controller)); } -void AudioRendererHost::IPCAudioSource::OnClose(AudioOutputStream* stream) { - // Push source doesn't need to know the stream so just pass in NULL. - if (!shared_socket_.get()) { - AutoLock auto_lock(lock_); - push_source_.OnClose(NULL); - } else { - shared_socket_->Close(); - } +void AudioRendererHost::OnError(media::AudioController* controller, + int error_code) { + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod(this, &AudioRendererHost::DoHandleError, + controller, error_code)); } -void AudioRendererHost::IPCAudioSource::OnError(AudioOutputStream* stream, - int code) { - host_->SendErrorMessage(route_id_, stream_id_); - // The following method call would cause this object to be destroyed on IO - // thread. - host_->DestroySource(this); +void AudioRendererHost::OnMoreData(media::AudioController* controller, + base::Time timestamp, + uint32 pending_bytes) { + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod(this, &AudioRendererHost::DoRequestMoreData, + controller, timestamp, pending_bytes)); } -void AudioRendererHost::IPCAudioSource::NotifyPacketReady( - uint32 decoded_packet_size) { - // Packet ready notifications do not happen in low latency mode. If they - // do something is horribly wrong. - DCHECK(!shared_socket_.get()); +void AudioRendererHost::DoCompleteCreation(media::AudioController* controller) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - AutoLock auto_lock(lock_); + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; - // If we don't have an outstanding request, we should take the data. - if (!outstanding_request_) { - NOTREACHED() << "Received an audio packet while there was no such request"; + if (!process_handle_) { + NOTREACHED() << "Renderer process handle is invalid."; + DeleteEntryOnError(entry); return; } - outstanding_request_ = false; + // 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; + } - // Don't write to push source and submit a new request if the last one - // replied with no data. This is likely due to data is depleted in the - // renderer process. - // If reported size is greater than capacity of the shared memory, we have - // an error. - if (decoded_packet_size && decoded_packet_size <= shared_memory_.max_size()) { - bool ok = push_source_.Write( - static_cast<char*>(shared_memory_.memory()), decoded_packet_size); + if (entry->controller->LowLatencyMode()) { + AudioSyncReader* reader = + static_cast<AudioSyncReader*>(entry->reader.get()); - // Submit packet request if we have written something. - if (ok) - SubmitPacketRequest(&auto_lock); - } -} +#if defined(OS_WIN) + base::SyncSocket::Handle foreign_socket_handle; +#else + base::FileDescriptor foreign_socket_handle; +#endif -void AudioRendererHost::IPCAudioSource::SubmitPacketRequest_Locked() { - lock_.AssertAcquired(); - // Submit a new request when these two conditions are fulfilled: - // 1. No outstanding request - // 2. There's space for data of the new request. - if (!outstanding_request_ && - (push_source_.UnProcessedBytes() <= buffer_capacity_)) { - outstanding_request_ = true; - - // This variable keeps track of the total amount of bytes buffered for - // the associated AudioOutputStream. This value should consist of bytes - // buffered in AudioOutputStream and those kept inside |push_source_|. - uint32 buffered_bytes = pending_bytes_ + push_source_.UnProcessedBytes(); - host_->Send( - new ViewMsg_RequestAudioPacket( - route_id_, - stream_id_, - buffered_bytes, - last_callback_time_.ToInternalValue())); - } -} + // 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; + } -void AudioRendererHost::IPCAudioSource::SubmitPacketRequest(AutoLock* alock) { - if (alock) { - SubmitPacketRequest_Locked(); - } else { - AutoLock auto_lock(lock_); - SubmitPacketRequest_Locked(); + SendMessage(new ViewMsg_NotifyLowLatencyAudioStreamCreated( + entry->render_view_id, entry->stream_id, foreign_memory_handle, + foreign_socket_handle, entry->shared_memory.max_size())); + return; } -} -void AudioRendererHost::IPCAudioSource::StartBuffering() { - SubmitPacketRequest(NULL); + // 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.max_size())); } -//----------------------------------------------------------------------------- -// AudioRendererHost implementations. +void AudioRendererHost::DoSendPlayingMessage( + media::AudioController* controller) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); -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(); + 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)); } -AudioRendererHost::~AudioRendererHost() { +void AudioRendererHost::DoSendPausedMessage( + media::AudioController* controller) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - DCHECK(sources_.empty()); -} -void AudioRendererHost::Destroy() { - // Post a message to the thread where this object should live and do the - // actual operations there. - ChromeThread::PostTask( - ChromeThread::IO, FROM_HERE, - NewRunnableMethod(this, &AudioRendererHost::OnDestroyed)); + 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)); } -// 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) { +void AudioRendererHost::DoRequestMoreData(media::AudioController* controller, + base::Time timestamp, + uint32 pending_bytes) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - process_id_ = process_id; - process_handle_ = process_handle; - ipc_sender_ = ipc_sender; + + // 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, + pending_bytes, + timestamp.ToInternalValue())); } -// Event received when IPC channel is closing. -void AudioRendererHost::IPCChannelClosing() { +void AudioRendererHost::DoHandleError(media::AudioController* controller, + int error_code) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - ipc_sender_ = NULL; - process_handle_ = 0; - process_id_ = 0; - DestroyAllSources(); + + 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)) @@ -519,179 +315,264 @@ void AudioRendererHost::OnCreateStream( const IPC::Message& msg, int stream_id, const ViewHostMsg_Audio_CreateStream_Params& params, bool low_latency) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - DCHECK(Lookup(msg.routing_id(), stream_id) == NULL); + DCHECK(LookupById(msg.routing_id(), stream_id) == NULL); // Limit the number of audio streams opened. This is to prevent using // excessive resources for a large number of audio streams. More // importantly it prevents instability on certain systems. // See bug: http://crbug.com/30242 - if (sources_.size() >= GetMaxAudioStreamsAllowed()) { + if (audio_entries_.size() >= GetMaxAudioStreamsAllowed()) { SendErrorMessage(msg.routing_id(), stream_id); return; } - IPCAudioSource* source = IPCAudioSource::CreateIPCAudioSource( - this, - process_id_, - msg.routing_id(), - stream_id, - process_handle_, - params.format, - params.channels, - params.sample_rate, - params.bits_per_sample, - params.packet_size, - low_latency); - - // If we have created the source successfully, adds it to the map. - if (source) { - sources_.insert( - std::make_pair( - SourceID(source->route_id(), source->stream_id()), source)); + // Select the hardwaer packet size if not specified. + uint32 hardware_packet_size = params.packet_size; + if (!hardware_packet_size) { + hardware_packet_size = SelectHardwarePacketSize(params.channels, + params.sample_rate, + params.bits_per_sample); } + + scoped_ptr<AudioEntry> entry(new AudioEntry()); + scoped_refptr<media::AudioController> controller = NULL; + 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 AudioController. + entry->reader.reset(reader.release()); + controller = + media::AudioController::CreateLowLatency( + this, params.format, params.channels, + params.sample_rate, + params.bits_per_sample, + hardware_packet_size, + entry->reader.get()); + } else { + // The choice of buffer capacity is based on experiment. + controller = + media::AudioController::Create(this, params.format, params.channels, + params.sample_rate, + params.bits_per_sample, + hardware_packet_size, + 3 * hardware_packet_size); + } + + if (!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->controller = controller; + entry->render_view_id = msg.routing_id(); + entry->stream_id = stream_id; + + // Create the shared memory and share with the renderer process. + if (!entry->shared_memory.Create(L"", false, false, hardware_packet_size) || + !entry->shared_memory.Map(entry->shared_memory.max_size())) { + // If creation of shared memory failed then close the controller and + // sends an error message. + controller->Close(); + SendErrorMessage(msg.routing_id(), stream_id); + return; + } + + // If everything is successful then add it to the map. + 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(ChromeThread::CurrentlyOn(ChromeThread::IO)); - IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); - if (source) { - source->Play(); - } else { + + 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(ChromeThread::CurrentlyOn(ChromeThread::IO)); - IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); - if (source) { - source->Pause(); - } else { + + 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(ChromeThread::CurrentlyOn(ChromeThread::IO)); - IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); - if (source) { - source->Flush(); - } else { + + 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(ChromeThread::CurrentlyOn(ChromeThread::IO)); - IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); - if (source) { - DestroySource(source); - } + + AudioEntry* entry = LookupById(msg.routing_id(), stream_id); + + // Note that closing an audio stream is a blocking operation. This call may + // block the IO thread for up to 100ms. + if (entry) + DeleteEntry(entry); } void AudioRendererHost::OnSetVolume(const IPC::Message& msg, int stream_id, double volume) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); - if (source) { - source->SetVolume(volume); - } else { + + 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(ChromeThread::CurrentlyOn(ChromeThread::IO)); - IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); - if (source) { - source->GetVolume(); - } else { - SendErrorMessage(msg.routing_id(), stream_id); - } + NOTREACHED() << "This message shouldn't be received"; } -void AudioRendererHost::OnNotifyPacketReady(const IPC::Message& msg, - int stream_id, uint32 packet_size) { +void AudioRendererHost::OnNotifyPacketReady( + const IPC::Message& msg, int stream_id, uint32 packet_size) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); - if (source) { - source->NotifyPacketReady(packet_size); - } else { + + 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.max_size()); + + if (!entry->pending_buffer_request) { + NOTREACHED() << "Buffer received but no such pending request"; } + entry->pending_buffer_request = false; + + // If the audio packet is empty then don't enqueue to controller. This will + // avoid excessive communication between browser and renderer when audio + // data is depleted. + if (!packet_size) + return; + + // Enqueue the data to media::AudioController. + entry->controller->EnqueueData( + reinterpret_cast<uint8*>(entry->shared_memory.memory()), + packet_size); } -void AudioRendererHost::OnDestroyed() { +void AudioRendererHost::DoDestroy() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + + // Reset IPC releated members. ipc_sender_ = NULL; process_handle_ = 0; - process_id_ = 0; - DestroyAllSources(); + + // Close all audio streams. + DeleteEntries(); + // Decrease the reference to this object, which may lead to self-destruction. Release(); } -void AudioRendererHost::OnSend(IPC::Message* message) { +void AudioRendererHost::SendMessage(IPC::Message* message) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - if (ipc_sender_) { + + if (ipc_sender_) ipc_sender_->Send(message); - } } -void AudioRendererHost::OnDestroySource(IPCAudioSource* source) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - if (source) { - sources_.erase(SourceID(source->route_id(), source->stream_id())); - source->Close(); - delete source; - } +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::DestroyAllSources() { +void AudioRendererHost::DeleteEntries() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - std::vector<IPCAudioSource*> sources; - for (SourceMap::iterator i = sources_.begin(); i != sources_.end(); ++i) { - sources.push_back(i->second); - } - for (size_t i = 0; i < sources.size(); ++i) { - DestroySource(sources[i]); + + while (!audio_entries_.empty()) { + DeleteEntry(audio_entries_.begin()->second); } - DCHECK(sources_.empty()); + DCHECK(audio_entries_.empty()); } -AudioRendererHost::IPCAudioSource* AudioRendererHost::Lookup(int route_id, - int stream_id) { +void AudioRendererHost::DeleteEntry(AudioEntry* entry) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - SourceMap::iterator i = sources_.find(SourceID(route_id, stream_id)); - if (i != sources_.end()) - return i->second; - return NULL; + + // Delete the entry when this method goes out of scope. + scoped_ptr<AudioEntry> entry_deleter(entry); + + // Close the audio stream then remove the entry. + entry->controller->Close(); + + // Entry the entry from the map. + audio_entries_.erase( + AudioEntryId(entry->render_view_id, entry->stream_id)); } -void AudioRendererHost::Send(IPC::Message* message) { - if (ChromeThread::CurrentlyOn(ChromeThread::IO)) { - OnSend(message); - } else { - ChromeThread::PostTask( - ChromeThread::IO, FROM_HERE, - NewRunnableMethod(this, &AudioRendererHost::OnSend, message)); - } +void AudioRendererHost::DeleteEntryOnError(AudioEntry* entry) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::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); + DeleteEntry(entry); } -void AudioRendererHost::SendErrorMessage(int32 render_view_id, - int32 stream_id) { - ViewMsg_AudioStreamState_Params state; - state.state = ViewMsg_AudioStreamState_Params::kError; - Send(new ViewMsg_NotifyAudioStreamStateChanged( - render_view_id, stream_id, state)); +AudioRendererHost::AudioEntry* AudioRendererHost::LookupById( + int route_id, int stream_id) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + + AudioEntryMap::iterator i = audio_entries_.find( + AudioEntryId(route_id, stream_id)); + if (i != audio_entries_.end()) + return i->second; + return NULL; } -void AudioRendererHost::DestroySource(IPCAudioSource* source) { - if (ChromeThread::CurrentlyOn(ChromeThread::IO)) { - OnDestroySource(source); - } else { - ChromeThread::PostTask( - ChromeThread::IO, FROM_HERE, - NewRunnableMethod(this, &AudioRendererHost::OnDestroySource, source)); +AudioRendererHost::AudioEntry* AudioRendererHost::LookupByController( + media::AudioController* controller) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::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; } |