summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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_;