diff options
-rw-r--r-- | chrome/browser/renderer_host/audio_renderer_host.cc | 801 | ||||
-rw-r--r-- | chrome/browser/renderer_host/audio_renderer_host.h | 329 | ||||
-rw-r--r-- | chrome/browser/renderer_host/audio_renderer_host_unittest.cc | 435 | ||||
-rw-r--r-- | chrome/browser/renderer_host/audio_sync_reader.cc | 70 | ||||
-rw-r--r-- | chrome/browser/renderer_host/audio_sync_reader.h | 54 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | media/audio/audio_controller.cc | 34 | ||||
-rw-r--r-- | media/audio/audio_controller.h | 3 | ||||
-rw-r--r-- | media/audio/fake_audio_output_stream.cc | 8 |
9 files changed, 939 insertions, 797 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; } diff --git a/chrome/browser/renderer_host/audio_renderer_host.h b/chrome/browser/renderer_host/audio_renderer_host.h index 4cee126..32da12b 100644 --- a/chrome/browser/renderer_host/audio_renderer_host.h +++ b/chrome/browser/renderer_host/audio_renderer_host.h @@ -3,11 +3,7 @@ // found in the LICENSE file. // // AudioRendererHost serves audio related requests from AudioRenderer which -// lives inside the render process and provide access to audio hardware. It maps -// an internal ID to AudioRendererHost::IPCAudioSource in a map, which is the -// actual object providing audio packets through IPC. It creates the actual -// AudioOutputStream object when requested by the renderer provided with -// render view id and stream id. +// lives inside the render process and provide access to audio hardware. // // This class is owned by BrowserRenderProcessHost, and instantiated on UI // thread, but all other operations and method calls (except Destroy()) happens @@ -22,20 +18,6 @@ // OnDestroyed(), audio output streams are destroyed and Release() is called // which may result in self-destruction. // -// AudioRendererHost::IPCAudioSource is a container of AudioOutputStream and -// provide audio packets to the associated AudioOutputStream through IPC. It -// performs the logic for buffering and controlling the AudioOutputStream. -// -// Here is a state diagram for the IPCAudioSource: -// -// .---------> [ Stopped ] <--------. -// | ^ | -// | | | -// *[ Created ] --> [ Playing ] --> [ Paused ] -// ^ | -// | | -// `-----------------' -// // Here's an example of a typical IPC dialog for audio: // // Renderer AudioRendererHost @@ -83,22 +65,51 @@ #include "base/ref_counted.h" #include "base/scoped_ptr.h" #include "base/shared_memory.h" -#include "base/sync_socket.h" #include "base/waitable_event.h" #include "chrome/browser/chrome_thread.h" #include "ipc/ipc_message.h" +#include "media/audio/audio_controller.h" #include "media/audio/audio_output.h" #include "media/audio/simple_sources.h" class AudioManager; struct ViewHostMsg_Audio_CreateStream_Params; -class AudioRendererHost - : public base::RefCountedThreadSafe< - AudioRendererHost, ChromeThread::DeleteOnIOThread> { - private: - class IPCAudioSource; +class AudioRendererHost : public base::RefCountedThreadSafe< + AudioRendererHost, + ChromeThread::DeleteOnIOThread>, + public media::AudioController::EventHandler { public: + typedef std::pair<int32, int> AudioEntryId; + + struct AudioEntry { + AudioEntry() + : render_view_id(0), + stream_id(0), + pending_buffer_request(false) { + } + + // The AudioController that manages the audio stream. + scoped_refptr<media::AudioController> controller; + + // Render view ID that requested the audio stream. + int32 render_view_id; + + // The audio stream ID in the render view. + int stream_id; + + // Shared memory for transmission of the audio data. + base::SharedMemory shared_memory; + + // The synchronous reader to be used by the controller. We have the + // ownership of the reader. + scoped_ptr<media::AudioController::SyncReader> reader; + + bool pending_buffer_request; + }; + + typedef std::map<AudioEntryId, AudioEntry*> AudioEntryMap; + // Called from UI thread from the owner of this object. AudioRendererHost(); @@ -106,7 +117,7 @@ class AudioRendererHost // destruction of streams in IO thread. void Destroy(); - //--------------------------------------------------------------------------- + ///////////////////////////////////////////////////////////////////////////// // The following public methods are called from ResourceMessageFilter in the // IO thread. @@ -121,165 +132,27 @@ class AudioRendererHost // If it was, message_was_ok will be false iff the message was corrupt. bool OnMessageReceived(const IPC::Message& message, bool* message_was_ok); - protected: - friend class ChromeThread; - friend class DeleteTask<AudioRendererHost>; - - // Destruction always happens on the IO thread (see DeleteOnIOThread above). - virtual ~AudioRendererHost(); - - //--------------------------------------------------------------------------- - // Helper methods called from IPCAudioSource or from this class, since - // methods in IPCAudioSource maybe called from hardware audio threads, these - // methods make sure the actual tasks happen on IO thread. - // These methods are virtual protected so we can mock these methods to test - // IPCAudioSource. - - // A helper method to send an IPC message to renderer process on IO thread. - virtual void Send(IPC::Message* message); - - // A helper method for sending error IPC messages. - virtual void SendErrorMessage(int32 render_view_id, int32 stream_id); - - // A helper method for calling OnDestroySource on IO thread. - virtual void DestroySource(IPCAudioSource* source); + ///////////////////////////////////////////////////////////////////////////// + // AudioController::EventHandler implementations. + virtual void OnCreated(media::AudioController* controller); + virtual void OnPlaying(media::AudioController* controller); + virtual void OnPaused(media::AudioController* controller); + virtual void OnError(media::AudioController* controller, int error_code); + virtual void OnMoreData(media::AudioController* controller, + base::Time timestamp, + uint32 pending_bytes); private: - friend class AudioRendererHost::IPCAudioSource; friend class AudioRendererHostTest; + friend class ChromeThread; + friend class DeleteTask<AudioRendererHost>; + friend class MockAudioRendererHost; FRIEND_TEST_ALL_PREFIXES(AudioRendererHostTest, CreateMockStream); FRIEND_TEST_ALL_PREFIXES(AudioRendererHostTest, MockStreamDataConversation); - // The container for AudioOutputStream and serves the audio packet received - // via IPC. - class IPCAudioSource : public AudioOutputStream::AudioSourceCallback { - public: - // Internal state of the source. - enum State { - kCreated, - kPlaying, - kPaused, - kClosed, - kError, - }; - - // Factory method for creating an IPCAudioSource, returns NULL if failed. - // The IPCAudioSource object will have an internal state of - // AudioOutputStream::STATE_CREATED after creation. - // If an IPCAudioSource is created successfully, a - // ViewMsg_NotifyAudioStreamCreated message is sent to the renderer. - // This factory method also starts requesting audio packet from the renderer - // after creation. The renderer will thus receive - // ViewMsg_RequestAudioPacket message. - static IPCAudioSource* CreateIPCAudioSource( - AudioRendererHost* host, // Host of this source. - int process_id, // Process ID of renderer. - int route_id, // Routing ID to RenderView. - int stream_id, // ID of this source. - base::ProcessHandle process_handle, // Process handle of renderer. - AudioManager::Format format, // Format of the stream. - int channels, // Number of channels. - int sample_rate, // Sampling frequency/rate. - char bits_per_sample, // Number of bits per sample. - uint32 packet_size, // Size of hardware packet. - bool low_latency // Use low-latency (socket) code - ); - ~IPCAudioSource(); - - // Methods to control playback of the stream. - // Starts the playback of this audio output stream. The internal state will - // be updated to AudioOutputStream::STATE_STARTED and the state update is - // sent to the renderer. - void Play(); - - // Pause this audio output stream. The audio output stream will stop - // reading from the |push_source_|. The internal state will be updated - // to AudioOutputStream::STATE_PAUSED and the state update is sent to - // the renderer. - void Pause(); - - // Discard all audio data buffered in this output stream. This method only - // has effect when the stream is paused. - void Flush(); - - // Closes the audio output stream. After calling this method all activities - // of the audio output stream are stopped. - void Close(); - - // Sets the volume of the audio output stream. There's no IPC messages - // sent back to the renderer upon success and failure. - void SetVolume(double volume); - - // Gets the volume of the audio output stream. - // ViewMsg_NotifyAudioStreamVolume is sent back to renderer with volume - // information if succeeded. - void GetVolume(); - - // Notify this source that buffer has been filled and is ready to be - // consumed. - void NotifyPacketReady(uint32 packet_size); - - // AudioSourceCallback methods. - virtual uint32 OnMoreData(AudioOutputStream* stream, void* dest, - uint32 max_size, uint32 pending_bytes); - virtual void OnClose(AudioOutputStream* stream); - virtual void OnError(AudioOutputStream* stream, int code); - - int process_id() { return process_id_; } - int route_id() { return route_id_; } - int stream_id() { return stream_id_; } - - private: - IPCAudioSource(AudioRendererHost* host, // Host of this source. - int process_id, // Process ID of renderer. - int route_id, // Routing ID to RenderView. - int stream_id, // ID of this source. - AudioOutputStream* stream, // Stream associated. - uint32 hardware_packet_size, - uint32 buffer_capacity); // Capacity of transportation - // buffer. - - // Check the condition of |outstanding_request_| and |push_source_| to - // determine if we should submit a new packet request. - void SubmitPacketRequest_Locked(); - - void SubmitPacketRequest(AutoLock* alock); - - // A helper method to start buffering. This method is used by - // CreateIPCAudioSource to submit a packet request. - void StartBuffering(); - - AudioRendererHost* host_; - int process_id_; - int route_id_; - int stream_id_; - AudioOutputStream* stream_; - uint32 hardware_packet_size_; - uint32 buffer_capacity_; - - State state_; - - base::SharedMemory shared_memory_; - scoped_ptr<base::SyncSocket> shared_socket_; - - // PushSource role is to buffer and it's only used in regular latency mode. - PushSource push_source_; - - // Flag that indicates there is an outstanding request. - bool outstanding_request_; - - // Number of bytes copied in the last OnMoreData call. - uint32 pending_bytes_; - base::Time last_callback_time_; - - // Protects: - // - |outstanding_requests_| - // - |last_copied_bytes_| - // - |push_source_| - Lock lock_; - }; + virtual ~AudioRendererHost(); - //--------------------------------------------------------------------------- + //////////////////////////////////////////////////////////////////////////// // Methods called on IO thread. // Returns true if the message is an audio related message and should be // handled by this class. @@ -288,85 +161,85 @@ class AudioRendererHost // Audio related IPC message handlers. // Creates an audio output stream with the specified format. If this call is // successful this object would keep an internal entry of the stream for the - // required properties. See IPCAudioSource::CreateIPCAudioSource() for more - // details. + // required properties. void OnCreateStream(const IPC::Message& msg, int stream_id, const ViewHostMsg_Audio_CreateStream_Params& params, bool low_latency); - // Starts buffering for the audio output stream. Delegates the start method - // call to the corresponding IPCAudioSource::Play(). - // ViewMsg_NotifyAudioStreamStateChanged with - // AudioOutputStream::AUDIO_STREAM_ERROR is sent back to renderer if the - // required IPCAudioSource is not found. + // Play the audio stream referenced by |stream_id|. void OnPlayStream(const IPC::Message& msg, int stream_id); - // Pauses the audio output stream. Delegates the pause method call to the - // corresponding IPCAudioSource::Pause(), - // ViewMsg_NotifyAudioStreamStateChanged with - // AudioOutputStream::AUDIO_STREAM_ERROR is sent back to renderer if the - // required IPCAudioSource is not found. + // Pause the audio stream referenced by |stream_id|. void OnPauseStream(const IPC::Message& msg, int stream_id); - // Discard all audio data buffered. + // Discard all audio data in stream referenced by |stream_id|. void OnFlushStream(const IPC::Message& msg, int stream_id); - // Closes the audio output stream, delegates the close method call to the - // corresponding IPCAudioSource::Close(), no returning IPC message to renderer - // upon success and failure. + // Close the audio stream referenced by |stream_id|. void OnCloseStream(const IPC::Message& msg, int stream_id); - // Set the volume for the stream specified. Delegates the SetVolume() method - // call to IPCAudioSource. No returning IPC message to renderer upon success. - // ViewMsg_NotifyAudioStreamStateChanged with - // AudioOutputStream::AUDIO_STREAM_ERROR is sent back to renderer if the - // required IPCAudioSource is not found. + // Set the volume of the audio stream referenced by |stream_id|. void OnSetVolume(const IPC::Message& msg, int stream_id, double volume); - // Gets the volume of the stream specified, delegates to corresponding - // IPCAudioSource::GetVolume(), see the method for more details. - // ViewMsg_NotifyAudioStreamStateChanged with - // AudioOutputStream::AUDIO_STREAM_ERROR is sent back to renderer if the - // required IPCAudioSource is not found. + // Get the volume of the audio stream referenced by |stream_id|. void OnGetVolume(const IPC::Message& msg, int stream_id); - // Notify packet has been prepared for stream, delegates to corresponding - // IPCAudioSource::NotifyPacketReady(), see the method for more details. - // ViewMsg_NotifyAudioStreamStateChanged with - // AudioOutputStream::AUDIO_STREAM_ERROR is sent back to renderer if the - // required IPCAudioSource is not found. + // Notify packet has been prepared for the audio stream. void OnNotifyPacketReady(const IPC::Message& msg, int stream_id, uint32 packet_size); - // Called on IO thread when this object needs to be destroyed and after - // Destroy() is called from owner of this class in UI thread. - void OnDestroyed(); + // Release all acquired resources and decrease reference to this object. + void DoDestroy(); + + // Complete the process of creating an audio stream. This will set up the + // shared memory or shared socket in low latency mode. + void DoCompleteCreation(media::AudioController* controller); + + // Send a state change message to the renderer. + void DoSendPlayingMessage(media::AudioController* controller); + void DoSendPausedMessage(media::AudioController* controller); + + // Request more data from the renderer. This method is used only in normal + // latency mode. + void DoRequestMoreData(media::AudioController* controller, + base::Time timestamp, + uint32 pending_bytes); + + // Handle error coming from audio stream. + void DoHandleError(media::AudioController* controller, int error_code); + + // A helper method to send an IPC message to renderer process on IO thread. + // This method is virtual for testing purpose. + virtual void SendMessage(IPC::Message* message); + + // Send an error message to the renderer. + void SendErrorMessage(int32 render_view_id, int32 stream_id); + + // Delete all audio entry and all audio streams + void DeleteEntries(); - // Sends IPC messages using ipc_sender_. - void OnSend(IPC::Message* message); + // Delete an audio entry and close the related audio stream. + void DeleteEntry(AudioEntry* entry); - // Closes the source, deletes it and removes it from the internal map. - // Destruction of source and associated stream should always be done by this - // method. *DO NOT* call this method from other than IPCAudioSource and from - // this class. - void OnDestroySource(IPCAudioSource* source); + // Delete audio entry and close the related audio stream due to an error, + // and error message is send to the renderer. + void DeleteEntryOnError(AudioEntry* entry); - // A helper method that destroy all IPCAudioSource and associated audio - // output streams. - void DestroyAllSources(); + // A helper method to look up a AudioEntry with a tuple of render view + // id and stream id. Returns NULL if not found. + AudioEntry* LookupById(int render_view_id, int stream_id); - // A helper method to look up a IPCAudioSource with a tuple of render view id - // and stream id. Returns NULL if not found. - IPCAudioSource* Lookup(int render_view_id, int stream_id); + // Search for a AudioEntry having the reference to |controller|. + // This method is used to look up an AudioEntry after a controller + // event is received. + AudioEntry* LookupByController(media::AudioController* controller); int process_id_; base::ProcessHandle process_handle_; IPC::Message::Sender* ipc_sender_; // A map of id to audio sources. - typedef std::pair<int32, int32> SourceID; - typedef std::map<SourceID, IPCAudioSource*> SourceMap; - SourceMap sources_; + AudioEntryMap audio_entries_; DISALLOW_COPY_AND_ASSIGN(AudioRendererHost); }; diff --git a/chrome/browser/renderer_host/audio_renderer_host_unittest.cc b/chrome/browser/renderer_host/audio_renderer_host_unittest.cc index 537ba9c..6d8f96a 100644 --- a/chrome/browser/renderer_host/audio_renderer_host_unittest.cc +++ b/chrome/browser/renderer_host/audio_renderer_host_unittest.cc @@ -2,60 +2,71 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/env_var.h" #include "base/message_loop.h" #include "base/process_util.h" #include "base/scoped_ptr.h" +#include "base/sync_socket.h" +#include "base/waitable_event.h" #include "chrome/browser/chrome_thread.h" #include "chrome/browser/renderer_host/audio_renderer_host.h" #include "chrome/common/render_messages.h" +#include "ipc/ipc_message_utils.h" +#include "media/audio/fake_audio_output_stream.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::_; using ::testing::DoAll; using ::testing::InSequence; +using ::testing::InvokeWithoutArgs; using ::testing::Return; using ::testing::SaveArg; using ::testing::SetArgumentPointee; -namespace { +static const int kInvalidId = -1; +static const int kRouteId = 200; +static const int kStreamId = 50; -const int kInvalidId = -1; -const int kProcessId = 100; -const int kRouteId = 200; -const uint32 kBufferCapacity = 65536; -const uint32 kPacketSize = 16384; - -} // namespace +static bool IsRunningHeadless() { + scoped_ptr<base::EnvVarGetter> env(base::EnvVarGetter::Create()); + if (env->HasEnv("CHROME_HEADLESS")) + return true; + return false; +} class MockAudioRendererHost : public AudioRendererHost { public: - MockAudioRendererHost() - : AudioRendererHost() { + MockAudioRendererHost() : shared_memory_length_(0) { + } + + virtual ~MockAudioRendererHost() { } // A list of mock methods. MOCK_METHOD4(OnRequestPacket, void(int routing_id, int stream_id, uint32 bytes_in_buffer, int64 message_timestamp)); - MOCK_METHOD3(OnStreamCreated, void(int routing_id, int stream_id, int length)); - - MOCK_METHOD3(OnStreamStateChanged, - void(int routing_id, int stream_id, - const ViewMsg_AudioStreamState_Params& state)); - + MOCK_METHOD3(OnLowLatencyStreamCreated, + void(int routing_id, int stream_id, int length)); + MOCK_METHOD2(OnStreamPlaying, void(int routing_id, int stream_id)); + MOCK_METHOD2(OnStreamPaused, void(int routing_id, int stream_id)); + MOCK_METHOD2(OnStreamError, void(int routing_id, int stream_id)); MOCK_METHOD3(OnStreamVolume, void(int routing_id, int stream_id, double volume)); base::SharedMemory* shared_memory() { return shared_memory_.get(); } + uint32 shared_memory_length() { return shared_memory_length_; } - protected: + base::SyncSocket* sync_socket() { return sync_socket_.get(); } + + private: // This method is used to dispatch IPC messages to the renderer. We intercept // these messages here and dispatch to our mock methods to verify the // conversation between this object and the renderer. - virtual void Send(IPC::Message* message) { + virtual void SendMessage(IPC::Message* message) { CHECK(message); // In this method we dispatch the messages to the according handlers as if @@ -64,6 +75,8 @@ class MockAudioRendererHost : public AudioRendererHost { IPC_BEGIN_MESSAGE_MAP(MockAudioRendererHost, *message) IPC_MESSAGE_HANDLER(ViewMsg_RequestAudioPacket, OnRequestPacket) IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamCreated, OnStreamCreated) + IPC_MESSAGE_HANDLER(ViewMsg_NotifyLowLatencyAudioStreamCreated, + OnLowLatencyStreamCreated) IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamStateChanged, OnStreamStateChanged) IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamVolume, OnStreamVolume) @@ -74,9 +87,6 @@ class MockAudioRendererHost : public AudioRendererHost { delete message; } - private: - virtual ~MockAudioRendererHost() {} - // These handler methods do minimal things and delegate to the mock methods. void OnRequestPacket(const IPC::Message& msg, int stream_id, uint32 bytes_in_buffer, int64 message_timestamp) { @@ -87,17 +97,53 @@ class MockAudioRendererHost : public AudioRendererHost { void OnStreamCreated(const IPC::Message& msg, int stream_id, base::SharedMemoryHandle handle, uint32 length) { // Maps the shared memory. - shared_memory_.reset(new base::SharedMemory(handle, true)); + shared_memory_.reset(new base::SharedMemory(handle, false)); + ASSERT_TRUE(shared_memory_->Map(length)); + ASSERT_TRUE(shared_memory_->memory()); + shared_memory_length_ = length; + + // And then delegate the call to the mock method. + OnStreamCreated(msg.routing_id(), stream_id, length); + } + + void OnLowLatencyStreamCreated(const IPC::Message& msg, int stream_id, + base::SharedMemoryHandle handle, +#if defined(OS_WIN) + base::SyncSocket::Handle socket_handle, +#else + base::FileDescriptor socket_descriptor, +#endif + uint32 length) { + // Maps the shared memory. + shared_memory_.reset(new base::SharedMemory(handle, false)); CHECK(shared_memory_->Map(length)); CHECK(shared_memory_->memory()); + shared_memory_length_ = length; + + // Create the SyncSocket using the handle. + base::SyncSocket::Handle sync_socket_handle; +#if defined(OS_WIN) + sync_socket_handle = socket_handle; +#else + sync_socket_handle = socket_descriptor.fd; +#endif + sync_socket_.reset(new base::SyncSocket(sync_socket_handle)); // And then delegate the call to the mock method. - OnStreamCreated(msg.routing_id(), stream_id, length); + OnLowLatencyStreamCreated(msg.routing_id(), stream_id, length); } void OnStreamStateChanged(const IPC::Message& msg, int stream_id, - const ViewMsg_AudioStreamState_Params& state) { - OnStreamStateChanged(msg.routing_id(), stream_id, state); + const ViewMsg_AudioStreamState_Params& params) { + if (params.state == ViewMsg_AudioStreamState_Params::kPlaying) { + OnStreamPlaying(msg.routing_id(), stream_id); + } else if (params.state == ViewMsg_AudioStreamState_Params::kPaused) { + OnStreamPaused(msg.routing_id(), stream_id); + } else if (params.state == ViewMsg_AudioStreamState_Params::kError) { + OnStreamError(msg.routing_id(), stream_id); + } else { + FAIL() << "Unknown stream state"; + } } void OnStreamVolume(const IPC::Message& msg, int stream_id, double volume) { @@ -105,14 +151,20 @@ class MockAudioRendererHost : public AudioRendererHost { } scoped_ptr<base::SharedMemory> shared_memory_; + scoped_ptr<base::SyncSocket> sync_socket_; + uint32 shared_memory_length_; DISALLOW_COPY_AND_ASSIGN(MockAudioRendererHost); }; +ACTION_P(QuitMessageLoop, message_loop) { + message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask()); +} + class AudioRendererHostTest : public testing::Test { public: AudioRendererHostTest() - : current_stream_id_(0) { + : mock_stream_(true) { } protected: @@ -121,9 +173,17 @@ class AudioRendererHostTest : public testing::Test { message_loop_.reset(new MessageLoop(MessageLoop::TYPE_IO)); io_thread_.reset(new ChromeThread(ChromeThread::IO, message_loop_.get())); host_ = new MockAudioRendererHost(); + + // Simulate IPC channel connected. + host_->IPCChannelConnected(base::GetCurrentProcId(), + base::GetCurrentProcessHandle(), + NULL); } virtual void TearDown() { + // Simulate closing the IPC channel. + host_->IPCChannelClosing(); + // This task post a task to message_loop_ to do internal destruction on // message_loop_. host_->Destroy(); @@ -137,90 +197,275 @@ class AudioRendererHostTest : public testing::Test { io_thread_.reset(); } - AudioRendererHost::IPCAudioSource* CreateAudioStream( - AudioManager::Format format) { + void Create() { InSequence s; - // 1. We will first receive a OnStreamCreated() signal. EXPECT_CALL(*host_, - OnStreamCreated(kRouteId, current_stream_id_, kPacketSize)); + OnStreamCreated(kRouteId, kStreamId, _)); + + // 2. First packet request will arrive. + EXPECT_CALL(*host_, OnRequestPacket(kRouteId, kStreamId, _, _)) + .WillOnce(QuitMessageLoop(message_loop_.get())); + + IPC::Message msg; + msg.set_routing_id(kRouteId); + + ViewHostMsg_Audio_CreateStream_Params params; + if (mock_stream_) + params.format = AudioManager::AUDIO_MOCK; + else + params.format = AudioManager::AUDIO_PCM_LINEAR; + params.channels = 2; + params.sample_rate = AudioManager::kAudioCDSampleRate; + params.bits_per_sample = 16; + params.packet_size = 0; + + // Send a create stream message to the audio output stream and wait until + // we receive the created message. + host_->OnCreateStream(msg, kStreamId, params, false); + message_loop_->Run(); + } + + void CreateLowLatency() { + InSequence s; + // We will first receive a OnLowLatencyStreamCreated() signal. + EXPECT_CALL(*host_, + OnLowLatencyStreamCreated(kRouteId, kStreamId, _)) + .WillOnce(QuitMessageLoop(message_loop_.get())); + + IPC::Message msg; + msg.set_routing_id(kRouteId); + + ViewHostMsg_Audio_CreateStream_Params params; + if (mock_stream_) + params.format = AudioManager::AUDIO_MOCK; + else + params.format = AudioManager::AUDIO_PCM_LINEAR; + params.channels = 2; + params.sample_rate = AudioManager::kAudioCDSampleRate; + params.bits_per_sample = 16; + params.packet_size = 0; + + // Send a create stream message to the audio output stream and wait until + // we receive the created message. + host_->OnCreateStream(msg, kStreamId, params, true); + message_loop_->Run(); + } + + void Close() { + // Send a message to AudioRendererHost to tell it we want to close the + // stream. + IPC::Message msg; + msg.set_routing_id(kRouteId); + host_->OnCloseStream(msg, kStreamId); + message_loop_->RunAllPending(); + } - // 2. First packet request will arrive. This request is sent by - // IPCAudioSource::CreateIPCAudioSource to start buffering. - EXPECT_CALL(*host_, OnRequestPacket(kRouteId, current_stream_id_, 0, _)); + void Play() { + EXPECT_CALL(*host_, OnStreamPlaying(kRouteId, kStreamId)) + .WillOnce(QuitMessageLoop(message_loop_.get())); - AudioRendererHost::IPCAudioSource* source = - AudioRendererHost::IPCAudioSource::CreateIPCAudioSource( - host_, - kProcessId, - kRouteId, - current_stream_id_, - base::GetCurrentProcessHandle(), - format, - 2, - AudioManager::kAudioCDSampleRate, - 16, - kPacketSize, - false); - EXPECT_TRUE(source); - EXPECT_EQ(kProcessId, source->process_id()); - EXPECT_EQ(kRouteId, source->route_id()); - EXPECT_EQ(current_stream_id_, source->stream_id()); - return source; + IPC::Message msg; + msg.set_routing_id(kRouteId); + host_->OnPlayStream(msg, kStreamId); + message_loop_->Run(); } - AudioRendererHost::IPCAudioSource* CreateRealStream() { - return CreateAudioStream(AudioManager::AUDIO_PCM_LINEAR); + void Pause() { + EXPECT_CALL(*host_, OnStreamPaused(kRouteId, kStreamId)) + .WillOnce(QuitMessageLoop(message_loop_.get())); + + IPC::Message msg; + msg.set_routing_id(kRouteId); + host_->OnPauseStream(msg, kStreamId); + message_loop_->Run(); } - AudioRendererHost::IPCAudioSource* CreateMockStream() { - return CreateAudioStream(AudioManager::AUDIO_MOCK); + void SetVolume(double volume) { + IPC::Message msg; + msg.set_routing_id(kRouteId); + host_->OnSetVolume(msg, kStreamId, volume); + message_loop_->RunAllPending(); } - int current_stream_id_; - scoped_refptr<MockAudioRendererHost> host_; - scoped_ptr<MessageLoop> message_loop_; + void NotifyPacketReady() { + EXPECT_CALL(*host_, OnRequestPacket(kRouteId, kStreamId, _, _)) + .WillOnce(QuitMessageLoop(message_loop_.get())); + + IPC::Message msg; + msg.set_routing_id(kRouteId); + memset(host_->shared_memory()->memory(), 0, host_->shared_memory_length()); + host_->OnNotifyPacketReady(msg, kStreamId, + host_->shared_memory_length()); + message_loop_->Run(); + } + + void SimulateError() { + // Find the first AudioController in the AudioRendererHost. + CHECK(host_->audio_entries_.size()) + << "Calls Create() before calling this method"; + media::AudioController* controller = + host_->audio_entries_.begin()->second->controller; + CHECK(controller) << "AudioController not found"; + + // Expect an error signal sent through IPC. + EXPECT_CALL(*host_, OnStreamError(kRouteId, kStreamId)) + .WillOnce(QuitMessageLoop(message_loop_.get())); + + // Simulate an error sent from the audio device. + host_->OnError(controller, 0); + message_loop_->Run(); + + // Expect the audio stream record is removed. + EXPECT_EQ(0u, host_->audio_entries_.size()); + } + + MessageLoop* message_loop() { return message_loop_.get(); } + MockAudioRendererHost* host() { return host_; } + void EnableRealDevice() { mock_stream_ = false; } private: + bool mock_stream_; + scoped_refptr<MockAudioRendererHost> host_; + scoped_ptr<MessageLoop> message_loop_; scoped_ptr<ChromeThread> io_thread_; + DISALLOW_COPY_AND_ASSIGN(AudioRendererHostTest); }; -TEST_F(AudioRendererHostTest, MockStreamDataConversation) { - scoped_ptr<AudioRendererHost::IPCAudioSource> source(CreateMockStream()); - - // We will receive packet requests until the buffer is full. We first send - // three packets of 16KB, then we send packets of 1KB until the buffer is - // full. Then there will no more packet requests. - InSequence s; - EXPECT_CALL(*host_, - OnRequestPacket(kRouteId, current_stream_id_, kPacketSize, _)); - EXPECT_CALL(*host_, - OnRequestPacket(kRouteId, current_stream_id_, 2 * kPacketSize, _)); - - const int kStep = kPacketSize / 4; - EXPECT_CALL(*host_, - OnRequestPacket(kRouteId, current_stream_id_, - 2 * kPacketSize + kStep, _)); - EXPECT_CALL(*host_, - OnRequestPacket(kRouteId, current_stream_id_, - 2 * kPacketSize + 2 * kStep, _)); - EXPECT_CALL(*host_, - OnRequestPacket(kRouteId, current_stream_id_, - 2 * kPacketSize + 3 * kStep, _)); - EXPECT_CALL(*host_, - OnRequestPacket(kRouteId, current_stream_id_, 3 * kPacketSize, _)); - ViewMsg_AudioStreamState_Params state; - EXPECT_CALL(*host_, OnStreamStateChanged(kRouteId, current_stream_id_, _)) - .WillOnce(SaveArg<2>(&state)); - - source->NotifyPacketReady(kPacketSize); - source->NotifyPacketReady(kPacketSize); - source->NotifyPacketReady(kStep); - source->NotifyPacketReady(kStep); - source->NotifyPacketReady(kStep); - source->NotifyPacketReady(kStep); - source->Play(); - EXPECT_EQ(ViewMsg_AudioStreamState_Params::kPlaying, state.state); - source->Close(); +TEST_F(AudioRendererHostTest, CreateAndClose) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Close(); +} + +TEST_F(AudioRendererHostTest, CreatePlayAndClose) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); + Close(); +} + +TEST_F(AudioRendererHostTest, CreatePlayPauseAndClose) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); + Pause(); + Close(); +} + +TEST_F(AudioRendererHostTest, SetVolume) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + SetVolume(0.5); + Play(); + Pause(); + Close(); + + // Expect the volume is set. + if (IsRunningHeadless()) { + EXPECT_EQ(0.5, FakeAudioOutputStream::GetLastFakeStream()->volume()); + } +} + +// Simulate the case where a stream is not properly closed. +TEST_F(AudioRendererHostTest, CreateAndShutdown) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); +} + +// Simulate the case where a stream is not properly closed. +TEST_F(AudioRendererHostTest, CreatePlayAndShutdown) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); +} + +// Simulate the case where a stream is not properly closed. +TEST_F(AudioRendererHostTest, CreatePlayPauseAndShutdown) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); + Pause(); +} + +TEST_F(AudioRendererHostTest, DataConversationMockStream) { + Create(); + + // Note that we only do notify three times because the buffer capacity is + // triple of one packet size. + NotifyPacketReady(); + NotifyPacketReady(); + NotifyPacketReady(); + Close(); +} + +TEST_F(AudioRendererHostTest, DataConversationRealStream) { + if (IsRunningHeadless()) + return; + EnableRealDevice(); + Create(); + Play(); + + // If this is a real audio device, the data conversation is not limited + // to the buffer capacity of AudioController. So we do 5 exchanges before + // we close the device. + for (int i = 0; i < 5; ++i) { + NotifyPacketReady(); + } + Close(); +} + +TEST_F(AudioRendererHostTest, SimulateError) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); + SimulateError(); +} + +// Simulate the case when an error is generated on the browser process, +// the audio device is closed but the render process try to close the +// audio stream again. +TEST_F(AudioRendererHostTest, SimulateErrorAndClose) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); + SimulateError(); + Close(); } + +TEST_F(AudioRendererHostTest, CreateLowLatencyAndClose) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + CreateLowLatency(); + Close(); +} + +// Simulate the case where a stream is not properly closed. +TEST_F(AudioRendererHostTest, CreateLowLatencyAndShutdown) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + CreateLowLatency(); +} + +// TODO(hclam): Add tests for data conversation in low latency mode. diff --git a/chrome/browser/renderer_host/audio_sync_reader.cc b/chrome/browser/renderer_host/audio_sync_reader.cc new file mode 100644 index 0000000..1cccfbe --- /dev/null +++ b/chrome/browser/renderer_host/audio_sync_reader.cc @@ -0,0 +1,70 @@ +// 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_sync_reader.h" + +#include "base/process_util.h" +#include "base/shared_memory.h" + +AudioSyncReader::AudioSyncReader(base::SharedMemory* shared_memory) + : shared_memory_(shared_memory) { +} + +AudioSyncReader::~AudioSyncReader() { +} + +// media::AudioController::SyncReader implementations. +void AudioSyncReader::UpdatePendingBytes(uint32 bytes) { + socket_->Send(&bytes, sizeof(bytes)); +} + +uint32 AudioSyncReader::Read(void* data, uint32 size) { + int read_size = std::min(size, shared_memory_->max_size()); + memcpy(data, shared_memory_->memory(), read_size); + memset(shared_memory_->memory(), 0, shared_memory_->max_size()); + return read_size; +} + +void AudioSyncReader::Close() { + socket_->Close(); +} + +bool AudioSyncReader::Init() { + base::SyncSocket* sockets[2] = {0}; + if (base::SyncSocket::CreatePair(sockets)) { + socket_.reset(sockets[0]); + foreign_socket_.reset(sockets[1]); + return true; + } + return false; +} + +#if defined(OS_WIN) +bool AudioSyncReader::PrepareForeignSocketHandle( + base::ProcessHandle process_handle, + base::SyncSocket::Handle* foreign_handle) { + ::DuplicateHandle(GetCurrentProcess(), foreign_socket_->handle(), + process_handle, foreign_handle, + 0, FALSE, DUPLICATE_SAME_ACCESS); + if (*foreign_handle != 0) { + // Note that this is just a way to get away with the unused variable + // warning. We want to return true here. + return foreign_socket_.release() != NULL; + } + return false; +} +#else +bool AudioSyncReader::PrepareForeignSocketHandle( + base::ProcessHandle process_handle, + base::FileDescriptor* foreign_handle) { + foreign_handle->fd = foreign_socket_->handle(); + foreign_handle->auto_close = false; + if (foreign_handle->fd != -1) { + // Note that this is just a way to get away with the unused variable + // warning. We want to return true here. + return foreign_socket_.release() != NULL; + } + return false; +} +#endif diff --git a/chrome/browser/renderer_host/audio_sync_reader.h b/chrome/browser/renderer_host/audio_sync_reader.h new file mode 100644 index 0000000..87783cd --- /dev/null +++ b/chrome/browser/renderer_host/audio_sync_reader.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef CHROME_BROWSER_RENDERER_HOST_AUDIO_SYNC_READER_ +#define CHROME_BROWSER_RENDERER_HOST_AUDIO_SYNC_READER_ + +#include "base/file_descriptor_posix.h" +#include "base/process.h" +#include "base/sync_socket.h" +#include "media/audio/audio_controller.h" + +namespace base { + +class SharedMemory; + +} + +// A AudioController::SyncReader implementation using SyncSocket. This is used +// by AudioController to provide a low latency data source for transmitting +// audio packets between the browser process and the renderer process. +class AudioSyncReader : public media::AudioController::SyncReader { + public: + explicit AudioSyncReader(base::SharedMemory* shared_memory); + + virtual ~AudioSyncReader(); + + // media::AudioController::SyncReader implementations. + virtual void UpdatePendingBytes(uint32 bytes); + virtual uint32 Read(void* data, uint32 size); + virtual void Close(); + + bool Init(); + bool PrepareForeignSocketHandle(base::ProcessHandle process_handle, +#if defined(OS_WIN) + base::SyncSocket::Handle* foreign_handle); +#else + base::FileDescriptor* foreign_handle); +#endif + + private: + base::SharedMemory* shared_memory_; + + // A pair of SyncSocket for transmitting audio data. + scoped_ptr<base::SyncSocket> socket_; + + // SyncSocket to be used by the renderer. The reference is released after + // PrepareForeignSocketHandle() is called and ran successfully. + scoped_ptr<base::SyncSocket> foreign_socket_; + + DISALLOW_COPY_AND_ASSIGN(AudioSyncReader); +}; + +#endif // CHROME_BROWSER_RENDERER_HOST_AUDIO_SYNC_READER_ diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index ff89c95..801b533 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1956,6 +1956,8 @@ 'browser/renderer_host/async_resource_handler.h', 'browser/renderer_host/audio_renderer_host.cc', 'browser/renderer_host/audio_renderer_host.h', + 'browser/renderer_host/audio_sync_reader.cc', + 'browser/renderer_host/audio_sync_reader.h', 'browser/renderer_host/backing_store.cc', 'browser/renderer_host/backing_store.h', 'browser/renderer_host/backing_store_manager.cc', diff --git a/media/audio/audio_controller.cc b/media/audio/audio_controller.cc index 21ecdfc..802a97c 100644 --- a/media/audio/audio_controller.cc +++ b/media/audio/audio_controller.cc @@ -34,7 +34,8 @@ namespace media { AudioController::AudioController(EventHandler* handler, uint32 capacity, SyncReader* sync_reader) : handler_(handler), - state_(kCreated), + volume_(0), + state_(kEmpty), hardware_pending_bytes_(0), buffer_capacity_(capacity), sync_reader_(sync_reader), @@ -42,7 +43,7 @@ AudioController::AudioController(EventHandler* handler, uint32 capacity, } AudioController::~AudioController() { - DCHECK(kClosed == state_ || kCreated == state_); + DCHECK(kClosed == state_); } // static @@ -157,6 +158,9 @@ void AudioController::EnqueueData(const uint8* data, uint32 size) { void AudioController::DoCreate(AudioManager::Format format, int channels, int sample_rate, int bits_per_sample, uint32 hardware_buffer_size) { + DCHECK_EQ(thread_.message_loop(), MessageLoop::current()); + DCHECK_EQ(kEmpty, state_); + // Create the stream in the first place. stream_ = AudioManager::GetAudioManager()->MakeAudioStream( format, channels, sample_rate, bits_per_sample); @@ -175,6 +179,13 @@ void AudioController::DoCreate(AudioManager::Format format, int channels, handler_->OnError(this, 0); return; } + // We have successfully opened the stream. Set the initial volume. + stream_->SetVolume(volume_); + + // Finally set the state to kCreated. + state_ = kCreated; + + // And then report we have been created. handler_->OnCreated(this); // If in normal latency mode then start buffering. @@ -252,13 +263,6 @@ void AudioController::DoClose() { stream_ = NULL; } - // If we are in low latency mode then also close the SyncReader. - // TODO(hclam): The shutdown procedure for low latency mode if not complete, - // especially when OnModeData() is blocked on SyncReader for read and the - // above Stop() would deadlock. - if (sync_reader_) - sync_reader_->Close(); - // Update the current state. Since the stream is closed at this point // there's no other threads reading |state_| so we don't need to lock. state_ = kClosed; @@ -267,10 +271,14 @@ void AudioController::DoClose() { void AudioController::DoSetVolume(double volume) { DCHECK_EQ(thread_.message_loop(), MessageLoop::current()); - if (state_ == kError || state_ == kEmpty) + // Saves the volume to a member first. We may not be able to set the volume + // right away but when the stream is created we'll set the volume. + volume_ = volume; + + if (state_ != kPlaying && state_ != kPaused && state_ != kCreated) return; - stream_->SetVolume(volume); + stream_->SetVolume(volume_); } void AudioController::DoReportError(int code) { @@ -311,7 +319,9 @@ uint32 AudioController::OnMoreData(AudioOutputStream* stream, void AudioController::OnClose(AudioOutputStream* stream) { // Push source doesn't need to know the stream so just pass in NULL. - if (!sync_reader_) { + if (LowLatencyMode()) { + sync_reader_->Close(); + } else { AutoLock auto_lock(lock_); push_source_.OnClose(NULL); } diff --git a/media/audio/audio_controller.h b/media/audio/audio_controller.h index 7b02af4..e7ef5f9 100644 --- a/media/audio/audio_controller.h +++ b/media/audio/audio_controller.h @@ -183,6 +183,9 @@ class AudioController : public base::RefCountedThreadSafe<AudioController>, EventHandler* handler_; AudioOutputStream* stream_; + // The current volume of the audio stream. + double volume_; + // |state_| is written on the audio controller thread and is read on the // hardware audio thread. These operations need to be locked. But lock // is not required for reading on the audio controller thread. diff --git a/media/audio/fake_audio_output_stream.cc b/media/audio/fake_audio_output_stream.cc index 8bfa28e..47b7d3e 100644 --- a/media/audio/fake_audio_output_stream.cc +++ b/media/audio/fake_audio_output_stream.cc @@ -49,8 +49,12 @@ void FakeAudioOutputStream::GetVolume(double* volume) { } void FakeAudioOutputStream::Close() { - callback_->OnClose(this); - callback_ = NULL; + // Calls |callback_| only if it is valid. We don't have |callback_| if + // we have not yet started. + if (callback_) { + callback_->OnClose(this); + callback_ = NULL; + } if (last_fake_stream_) delete last_fake_stream_; |