// Copyright (c) 2006-2009 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 "base/histogram.h" #include "base/lock.h" #include "base/message_loop.h" #include "base/process.h" #include "base/shared_memory.h" #include "base/waitable_event.h" #include "chrome/browser/renderer_host/audio_renderer_host.h" #include "chrome/common/ipc_logging.h" #include "chrome/common/render_messages.h" namespace { void RecordRoundTripLatency(base::TimeDelta latency) { static ThreadSafeHistogram histogram("Audio.IPC_RoundTripLatency", 1, 1000, 100); histogram.AddTime(latency); } void RecordReceiveLatency(base::TimeDelta latency) { static ThreadSafeHistogram histogram("Audio.IPC_Browser_ReceiveLatency", 1, 500, 100); histogram.AddTime(latency); } void RecordProcessTime(base::TimeDelta latency) { static ThreadSafeHistogram histogram("Audio.IPC_Browser_ProcessTime", 1, 100, 100); histogram.AddTime(latency); } } // namespace //----------------------------------------------------------------------------- // AudioRendererHost::IPCAudioSource implementations. AudioRendererHost::IPCAudioSource::IPCAudioSource( AudioRendererHost* host, int process_id, int route_id, int stream_id, AudioOutputStream* stream, size_t packet_size) : host_(host), process_id_(process_id), route_id_(route_id), stream_id_(stream_id), stream_(stream), packet_size_(packet_size), state_(AudioOutputStream::STATE_CREATED), stop_providing_packets_(false), packet_read_event_(false, false), last_packet_size_(0) { } AudioRendererHost::IPCAudioSource::~IPCAudioSource() { } // 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, size_t packet_size) { // Create the stream in the first place. AudioOutputStream* stream = AudioManager::GetAudioManager()->MakeAudioStream( format, channels, sample_rate, bits_per_sample); if (stream && !stream->Open(packet_size)) { stream->Close(); stream = NULL; } if (stream) { IPCAudioSource* source = new IPCAudioSource( host, process_id, route_id, stream_id, stream, packet_size); // If we can open the stream, proceed with sharing the shared memory. base::SharedMemoryHandle foreign_memory_handle; // Try to create, map and share the memory for the renderer process. // If they all succeeded then send a message to renderer to indicate // success. if (source->shared_memory_.Create(L"", false, false, packet_size) && source->shared_memory_.Map(packet_size) && source->shared_memory_.ShareToProcess(process_handle, &foreign_memory_handle)) { host->Send(new ViewMsg_NotifyAudioStreamCreated( route_id, stream_id, foreign_memory_handle, packet_size)); return source; } source->Close(); delete source; } host->SendErrorMessage(route_id, stream_id, 0); return NULL; } void AudioRendererHost::IPCAudioSource::Start() { // Only perform the start logic if this source has just created. if (!stream_ || state_ != AudioOutputStream::STATE_CREATED) return; // We don't start the stream immediately but prefetch some initial buffers // so as to fill all internal buffers of the AudioOutputStream. The number // of buffers to prefetch can be determined by // AudioOutputStream::GetNumBuffers(). if (stream_->GetNumBuffers()) { // If the audio output stream does have internal buffer(s), request a // packet from renderer and start the prefetching. host_->Send(new ViewMsg_RequestAudioPacket(route_id_, stream_id_)); } else { // If the audio output stream does not use any internal buffers, we are // safe to start it here. state_ = AudioOutputStream::STATE_STARTED; stream_->Start(this); host_->Send(new ViewMsg_NotifyAudioStreamStateChanged( route_id_, stream_id_, AudioOutputStream::STATE_STARTED, 0)); } } void AudioRendererHost::IPCAudioSource::Close() { // We need to wake up all waiting audio thread before calling stop. StopWaitingForPacket(); if (!stream_) return; stream_->Stop(); stream_->Close(); // After stream is closed it is destroyed, so don't keep a reference to it. stream_ = NULL; } void AudioRendererHost::IPCAudioSource::SetVolume(double left, double right) { // TODO(hclam): maybe send an error message back to renderer if this object // is in a wrong state. if (!stream_) return; stream_->SetVolume(left, right); } 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 left_channel, right_channel; stream_->GetVolume(&left_channel, &right_channel); host_->Send(new ViewMsg_NotifyAudioStreamVolume(route_id_, stream_id_, left_channel, right_channel)); } size_t AudioRendererHost::IPCAudioSource::OnMoreData(AudioOutputStream* stream, void* dest, size_t max_size) { #ifdef IPC_MESSAGE_LOG_ENABLED base::Time tick_start = base::Time::Now(); #endif { AutoLock auto_lock(lock_); // If we are ever stopped, don't ask for more audio packet from the // renderer. if (stop_providing_packets_) return 0; } // If we have an initial packet, use it immediately only in IO thread. // There's a case when IO thread is blocked and audio hardware thread can // reach here to consume initial packets. if (MessageLoop::current() == host_->io_loop()) { if (!initial_buffers_.empty()) { uint8* initial_packet = initial_buffers_.front().first; size_t initial_packet_size = initial_buffers_.front().second; initial_buffers_.pop_front(); size_t copied = SafeCopyBuffer(dest, max_size, initial_packet, initial_packet_size); delete [] initial_packet; return copied; } NOTREACHED(); } // We reach here because we ran out of initial packets, we need to ask the // renderer to give us more. In this case we have to wait until the renderer // gives us packet so we can't sleep on IO thread. DCHECK(MessageLoop::current() != host_->io_loop()); // Send an IPC message to request audio packet from renderer and wait on the // audio hardware thread. host_->Send(new ViewMsg_RequestAudioPacket(route_id_, stream_id_)); packet_read_event_.Wait(); size_t last_packet_size = 0; { AutoLock auto_lock(lock_); last_packet_size = last_packet_size_; } size_t copied = SafeCopyBuffer(dest, max_size, shared_memory_.memory(), last_packet_size); #ifdef IPC_MESSAGE_LOG_ENABLED // The logging to round trip latency doesn't have dependency on IPC logging. // But it's good we use IPC logging to trigger logging of total latency. if (IPC::Logging::current()->Enabled()) RecordRoundTripLatency(base::Time::Now() - tick_start); #endif return copied; } void AudioRendererHost::IPCAudioSource::OnClose(AudioOutputStream* stream) { StopWaitingForPacket(); } void AudioRendererHost::IPCAudioSource::OnError(AudioOutputStream* stream, int code) { host_->SendErrorMessage(route_id_, stream_id_, code); // The following method call would cause this object to be destroyed on IO // thread. host_->DestroySource(this); } void AudioRendererHost::IPCAudioSource::NotifyPacketReady(size_t packet_size) { if (packet_size > packet_size_) { // If reported size is greater than capacity of the shared memory, close the // stream. host_->SendErrorMessage(route_id_, stream_id_, 0); // We don't need to do packet_read_event_.Signal() here because the // contained stream should be closed by the following call and OnClose will // be received. host_->DestroySource(this); return; } if (state_ == AudioOutputStream::STATE_CREATED) { // If we are in a created state, that means we are performing prefetching. uint8* packet = new uint8[packet_size]; memcpy(packet, shared_memory_.memory(), packet_size); initial_buffers_.push_back(std::make_pair(packet, packet_size)); // If there's not enough initial packets prepared, ask more. if (initial_buffers_.size() < stream_->GetNumBuffers()) { host_->Send(new ViewMsg_RequestAudioPacket(route_id_, stream_id_)); } else { state_ = AudioOutputStream::STATE_STARTED; stream_->Start(this); host_->Send(new ViewMsg_NotifyAudioStreamStateChanged( route_id_, stream_id_, AudioOutputStream::STATE_STARTED, 0)); } } else { AutoLock auto_lock(lock_); last_packet_size_ = packet_size; packet_read_event_.Signal(); } } void AudioRendererHost::IPCAudioSource::StopWaitingForPacket() { AutoLock auto_lock(lock_); stop_providing_packets_ = true; last_packet_size_ = 0; packet_read_event_.Signal(); } size_t AudioRendererHost::IPCAudioSource::SafeCopyBuffer( void* dest, size_t dest_size, const void* src, size_t src_size) { if (src_size > dest_size) { host_->SendErrorMessage(route_id_, stream_id_, 0); host_->DestroySource(this); return 0; } memcpy(dest, src, src_size); return src_size; } //----------------------------------------------------------------------------- // AudioRendererHost implementations. AudioRendererHost::AudioRendererHost(MessageLoop* message_loop) : process_id_(0), process_handle_(0), ipc_sender_(NULL), io_loop_(message_loop) { // Make sure we perform actual initialization operations in the thread where // this object should live. io_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &AudioRendererHost::OnInitialized)); } AudioRendererHost::~AudioRendererHost() { } void AudioRendererHost::Destroy() { // Post a message to the thread where this object should live and do the // actual operations there. io_loop_->PostTask( FROM_HERE, NewRunnableMethod(this, &AudioRendererHost::OnDestroyed)); } // 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(MessageLoop::current() == io_loop_); process_id_ = process_id; process_handle_ = process_handle; ipc_sender_ = ipc_sender; } // Event received when IPC channel is closing. void AudioRendererHost::IPCChannelClosing() { DCHECK(MessageLoop::current() == io_loop_); ipc_sender_ = NULL; process_handle_ = 0; process_id_ = 0; DestroyAllSources(); } 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_StartAudioStream, OnStartStream) 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_StartAudioStream::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) { DCHECK(MessageLoop::current() == io_loop_); DCHECK(Lookup(msg.routing_id(), stream_id) == NULL); 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); // 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)); } } void AudioRendererHost::OnStartStream(const IPC::Message& msg, int stream_id) { DCHECK(MessageLoop::current() == io_loop_); IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); if (source) { source->Start(); } else { SendErrorMessage(msg.routing_id(), stream_id, 0); } } void AudioRendererHost::OnCloseStream(const IPC::Message& msg, int stream_id) { DCHECK(MessageLoop::current() == io_loop_); IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); if (source) { DestroySource(source); } } void AudioRendererHost::OnSetVolume(const IPC::Message& msg, int stream_id, double left_channel, double right_channel) { DCHECK(MessageLoop::current() == io_loop_); IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); if (source) { source->SetVolume(left_channel, right_channel); } else { SendErrorMessage(msg.routing_id(), stream_id, 0); } } void AudioRendererHost::OnGetVolume(const IPC::Message& msg, int stream_id) { DCHECK(MessageLoop::current() == io_loop_); IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); if (source) { source->GetVolume(); } else { SendErrorMessage(msg.routing_id(), stream_id, 0); } } void AudioRendererHost::OnNotifyPacketReady(const IPC::Message& msg, int stream_id, size_t packet_size) { DCHECK(MessageLoop::current() == io_loop_); IPCAudioSource* source = Lookup(msg.routing_id(), stream_id); if (source) { source->NotifyPacketReady(packet_size); } else { SendErrorMessage(msg.routing_id(), stream_id, 0); } #ifdef IPC_MESSAGE_LOG_ENABLED if (IPC::Logging::current()->Enabled()) { RecordReceiveLatency(base::Time::FromInternalValue(msg.received_time()) - base::Time::FromInternalValue(msg.sent_time())); RecordProcessTime(base::Time::Now() - base::Time::FromInternalValue(msg.received_time())); } #endif } void AudioRendererHost::OnInitialized() { DCHECK(MessageLoop::current() == io_loop_); // Increase the ref count of this object so it is active until we do // Release(). AddRef(); } void AudioRendererHost::OnDestroyed() { DCHECK(MessageLoop::current() == io_loop_); ipc_sender_ = NULL; process_handle_ = 0; process_id_ = 0; DestroyAllSources(); // Decrease the reference to this object, which may lead to self-destruction. Release(); } void AudioRendererHost::OnSend(IPC::Message* message) { DCHECK(MessageLoop::current() == io_loop_); if (ipc_sender_) { ipc_sender_->Send(message); } } void AudioRendererHost::OnDestroySource(IPCAudioSource* source) { DCHECK(MessageLoop::current() == io_loop_); if (source) { sources_.erase(SourceID(source->route_id(), source->stream_id())); source->Close(); delete source; } } void AudioRendererHost::DestroyAllSources() { DCHECK(MessageLoop::current() == io_loop_); std::vector 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]); } DCHECK(sources_.empty()); } AudioRendererHost::IPCAudioSource* AudioRendererHost::Lookup(int route_id, int stream_id) { DCHECK(MessageLoop::current() == io_loop_); SourceMap::iterator i = sources_.find(SourceID(route_id, stream_id)); if (i != sources_.end()) return i->second; return NULL; } void AudioRendererHost::Send(IPC::Message* message) { if (MessageLoop::current() == io_loop_) { OnSend(message); } else { // TODO(hclam): make sure it's always safe to post a task to IO loop. // It is possible that IO message loop is destroyed but there's still some // dangling audio hardware threads that try to call this method. io_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &AudioRendererHost::OnSend, message)); } } void AudioRendererHost::SendErrorMessage(int32 render_view_id, int32 stream_id, int info) { Send(new ViewMsg_NotifyAudioStreamStateChanged( render_view_id, stream_id, AudioOutputStream::STATE_ERROR, info)); } void AudioRendererHost::DestroySource(IPCAudioSource* source) { if (MessageLoop::current() == io_loop_) { OnDestroySource(source); } else { // TODO(hclam): make sure it's always safe to post a task to IO loop. // It is possible that IO message loop is destroyed but there's still some // dangling audio hardware threads that try to call this method. io_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &AudioRendererHost::OnDestroySource, source)); } }