summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-07 18:36:51 +0000
committerhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-07 18:36:51 +0000
commit94155a6b13969e7bb5cd96a6acf03277d12d9ae7 (patch)
tree773bbc2df939c85cb41ee1ae5fd6044f7199d3ed
parent62ede11c038242e242579cb312549acb7e64b518 (diff)
downloadchromium_src-94155a6b13969e7bb5cd96a6acf03277d12d9ae7.zip
chromium_src-94155a6b13969e7bb5cd96a6acf03277d12d9ae7.tar.gz
chromium_src-94155a6b13969e7bb5cd96a6acf03277d12d9ae7.tar.bz2
Rewrite AudioRendererHost to use AudioController
This change will move all the audio device methods calls to AudioController so we can move all the audio related methods calls off the IO thread. This change will let AudioRendererHost to use the AudioController API. This involves rewriting the whole AudioRendererHost. After this patch we can implement proper pause operations that is only possible if they are hosted on a separated thread due to their blocking nature. Normal latency mode is fully covered by unit tests, including audio control operations and data conversation. Low latency mode using SyncSocket is tested only for stream creation and still need to handle cases during shutdown when we want SyncSocket to return immediately. TEST=unit_tests --gtest_filter=AudioRendererTest.* BUG=39885 Review URL: http://codereview.chromium.org/2850016 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@51743 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/renderer_host/audio_renderer_host.cc801
-rw-r--r--chrome/browser/renderer_host/audio_renderer_host.h329
-rw-r--r--chrome/browser/renderer_host/audio_renderer_host_unittest.cc435
-rw-r--r--chrome/browser/renderer_host/audio_sync_reader.cc70
-rw-r--r--chrome/browser/renderer_host/audio_sync_reader.h54
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--media/audio/audio_controller.cc34
-rw-r--r--media/audio/audio_controller.h3
-rw-r--r--media/audio/fake_audio_output_stream.cc8
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_;