summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-05 02:35:27 +0000
committerhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-05 02:35:27 +0000
commit5c87288b5c019bb8bb728c5e213a38ec1917bab5 (patch)
tree32f6e420aade6248ef1eceb6aae819d3533f2765
parent4d4c32c6d0f9afda02dd545d7d7b2bc3625e9cd0 (diff)
downloadchromium_src-5c87288b5c019bb8bb728c5e213a38ec1917bab5.zip
chromium_src-5c87288b5c019bb8bb728c5e213a38ec1917bab5.tar.gz
chromium_src-5c87288b5c019bb8bb728c5e213a38ec1917bab5.tar.bz2
Changed to use PushSource for the intermediate buffer
between the IPC layer and the audio hardware interface. We have completely moved away from being blocking in AudioRendererHost. Since we'll be using PushSource for a longer period of buffering. It's necessary to have Play/Pause functionality in the AudioOutputStream, this is simulated by start/stop the AudioOutputStream multiple times. Review URL: http://codereview.chromium.org/114069 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17707 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/renderer_host/audio_renderer_host.cc275
-rw-r--r--chrome/browser/renderer_host/audio_renderer_host.h128
-rw-r--r--chrome/common/render_messages.h13
-rw-r--r--chrome/common/render_messages_internal.h6
-rw-r--r--chrome/renderer/media/audio_renderer_impl.cc116
-rw-r--r--chrome/renderer/media/audio_renderer_impl.h16
-rw-r--r--media/audio/win/audio_output_win_unittest.cc39
-rw-r--r--media/audio/win/waveout_output_win.cc2
8 files changed, 407 insertions, 188 deletions
diff --git a/chrome/browser/renderer_host/audio_renderer_host.cc b/chrome/browser/renderer_host/audio_renderer_host.cc
index 1a35be3..f07d8e5 100644
--- a/chrome/browser/renderer_host/audio_renderer_host.cc
+++ b/chrome/browser/renderer_host/audio_renderer_host.cc
@@ -32,6 +32,17 @@ void RecordProcessTime(base::TimeDelta latency) {
histogram.AddTime(latency);
}
+// This constant governs the hardware audio buffer size, this value should be
+// choosen carefully and is platform specific.
+const int kSamplesPerHardwarePacket = 8192;
+
+const size_t kMegabytes = 1024 * 1024;
+
+// The following parameters limit the request buffer and packet size from the
+// renderer to avoid renderer from requesting too much memory.
+const size_t kMaxDecodedPacketSize = 2 * kMegabytes;
+const size_t kMaxBufferCapacity = 5 * kMegabytes;
+
} // namespace
//-----------------------------------------------------------------------------
@@ -43,17 +54,20 @@ AudioRendererHost::IPCAudioSource::IPCAudioSource(
int route_id,
int stream_id,
AudioOutputStream* stream,
- size_t packet_size)
+ size_t hardware_packet_size,
+ size_t decoded_packet_size,
+ size_t buffer_capacity)
: host_(host),
process_id_(process_id),
route_id_(route_id),
stream_id_(stream_id),
stream_(stream),
- packet_size_(packet_size),
+ hardware_packet_size_(hardware_packet_size),
+ decoded_packet_size_(decoded_packet_size),
+ buffer_capacity_(buffer_capacity),
state_(AudioOutputStream::STATE_CREATED),
- stop_providing_packets_(false),
- packet_read_event_(false, false),
- last_packet_size_(0) {
+ push_source_(hardware_packet_size),
+ outstanding_request_(false) {
}
AudioRendererHost::IPCAudioSource::~IPCAudioSource() {
@@ -71,34 +85,60 @@ AudioRendererHost::IPCAudioSource*
int channels,
int sample_rate,
char bits_per_sample,
- size_t packet_size) {
+ size_t decoded_packet_size,
+ size_t buffer_capacity) {
+ // Perform come preliminary checks on the parameters.
+ // Make sure the renderer didn't ask for too much memory.
+ if (buffer_capacity > kMaxBufferCapacity ||
+ decoded_packet_size > kMaxDecodedPacketSize)
+ return NULL;
+
+ // Make sure the packet size and buffer capacity parameters are valid.
+ if (buffer_capacity < decoded_packet_size)
+ return NULL;
+
// Create the stream in the first place.
AudioOutputStream* stream =
AudioManager::GetAudioManager()->MakeAudioStream(
format, channels, sample_rate, bits_per_sample);
- if (stream && !stream->Open(packet_size)) {
+
+ size_t hardware_packet_size = kSamplesPerHardwarePacket * channels *
+ bits_per_sample / 8;
+ 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, packet_size);
+ host,
+ process_id,
+ route_id,
+ stream_id,
+ stream,
+ hardware_packet_size,
+ decoded_packet_size,
+ buffer_capacity);
// If we can open the stream, proceed with sharing the shared memory.
base::SharedMemoryHandle foreign_memory_handle;
// Try to create, map and share the memory for the renderer process.
// If they all succeeded then send a message to renderer to indicate
// success.
- if (source->shared_memory_.Create(L"", false, false, packet_size) &&
- source->shared_memory_.Map(packet_size) &&
+ if (source->shared_memory_.Create(L"",
+ false,
+ false,
+ decoded_packet_size) &&
+ source->shared_memory_.Map(decoded_packet_size) &&
source->shared_memory_.ShareToProcess(process_handle,
&foreign_memory_handle)) {
host->Send(new ViewMsg_NotifyAudioStreamCreated(
- route_id, stream_id, foreign_memory_handle, packet_size));
+ route_id, stream_id, foreign_memory_handle, decoded_packet_size));
+
+ // Also request the first packet to kick start the pre-rolling.
+ source->StartBuffering();
return source;
}
-
source->Close();
delete source;
}
@@ -108,38 +148,47 @@ AudioRendererHost::IPCAudioSource*
}
void AudioRendererHost::IPCAudioSource::Start() {
- // Only perform the start logic if this source has just created.
- if (!stream_ || state_ != AudioOutputStream::STATE_CREATED)
+ // We can start from created or paused state.
+ if (!stream_ ||
+ (state_ != AudioOutputStream::STATE_CREATED &&
+ state_ != AudioOutputStream::STATE_PAUSED))
return;
- // We don't start the stream immediately but prefetch some initial buffers
- // so as to fill all internal buffers of the AudioOutputStream. The number
- // of buffers to prefetch can be determined by
- // AudioOutputStream::GetNumBuffers().
- if (stream_->GetNumBuffers()) {
- // If the audio output stream does have internal buffer(s), request a
- // packet from renderer and start the prefetching.
- host_->Send(new ViewMsg_RequestAudioPacket(route_id_, stream_id_));
- } else {
- // If the audio output stream does not use any internal buffers, we are
- // safe to start it here.
- state_ = AudioOutputStream::STATE_STARTED;
- stream_->Start(this);
- host_->Send(new ViewMsg_NotifyAudioStreamStateChanged(
- route_id_, stream_id_, AudioOutputStream::STATE_STARTED, 0));
- }
+ stream_->Start(this);
+
+ // Update the state and notify renderer.
+ state_ = AudioOutputStream::STATE_STARTED;
+ host_->Send(new ViewMsg_NotifyAudioStreamStateChanged(
+ route_id_, stream_id_, state_, 0));
}
-void AudioRendererHost::IPCAudioSource::Close() {
- // We need to wake up all waiting audio thread before calling stop.
- StopWaitingForPacket();
+void AudioRendererHost::IPCAudioSource::Pause() {
+ // We can pause from started state.
+ if (!stream_ ||
+ state_ != AudioOutputStream::STATE_STARTED)
+ return;
+
+ // TODO(hclam): use stop to simulate pause, make sure the AudioOutpusStream
+ // can be started again after stop.
+ stream_->Stop();
+ // Update the state and notify renderer.
+ state_ = AudioOutputStream::STATE_PAUSED;
+ host_->Send(new ViewMsg_NotifyAudioStreamStateChanged(
+ route_id_, stream_id_, state_, 0));
+}
+
+void AudioRendererHost::IPCAudioSource::Close() {
if (!stream_)
return;
+
stream_->Stop();
stream_->Close();
// After stream is closed it is destroyed, so don't keep a reference to it.
stream_ = NULL;
+
+ // Update the current state.
+ state_ = AudioOutputStream::STATE_STOPPED;
}
void AudioRendererHost::IPCAudioSource::SetVolume(double left, double right) {
@@ -164,62 +213,16 @@ void AudioRendererHost::IPCAudioSource::GetVolume() {
size_t AudioRendererHost::IPCAudioSource::OnMoreData(AudioOutputStream* stream,
void* dest,
size_t max_size) {
-#ifdef IPC_MESSAGE_LOG_ENABLED
- base::Time tick_start = base::Time::Now();
-#endif
+ size_t size = push_source_.OnMoreData(stream, dest, max_size);
{
AutoLock auto_lock(lock_);
- // If we are ever stopped, don't ask for more audio packet from the
- // renderer.
- if (stop_providing_packets_)
- return 0;
+ SubmitPacketRequest(&auto_lock);
}
-
- // If we have an initial packet, use it immediately only in IO thread.
- // There's a case when IO thread is blocked and audio hardware thread can
- // reach here to consume initial packets.
- if (MessageLoop::current() == host_->io_loop()) {
- if (!initial_buffers_.empty()) {
- uint8* initial_packet = initial_buffers_.front().first;
- size_t initial_packet_size = initial_buffers_.front().second;
- initial_buffers_.pop_front();
- size_t copied =
- SafeCopyBuffer(dest, max_size, initial_packet, initial_packet_size);
- delete [] initial_packet;
- return copied;
- }
- NOTREACHED();
- }
-
- // We reach here because we ran out of initial packets, we need to ask the
- // renderer to give us more. In this case we have to wait until the renderer
- // gives us packet so we can't sleep on IO thread.
- DCHECK(MessageLoop::current() != host_->io_loop());
-
- // Send an IPC message to request audio packet from renderer and wait on the
- // audio hardware thread.
- host_->Send(new ViewMsg_RequestAudioPacket(route_id_, stream_id_));
- packet_read_event_.Wait();
-
- size_t last_packet_size = 0;
- {
- AutoLock auto_lock(lock_);
- last_packet_size = last_packet_size_;
- }
-
- size_t copied = SafeCopyBuffer(dest, max_size,
- shared_memory_.memory(), last_packet_size);
-#ifdef IPC_MESSAGE_LOG_ENABLED
- // The logging to round trip latency doesn't have dependency on IPC logging.
- // But it's good we use IPC logging to trigger logging of total latency.
- if (IPC::Logging::current()->Enabled())
- RecordRoundTripLatency(base::Time::Now() - tick_start);
-#endif
- return copied;
+ return size;
}
void AudioRendererHost::IPCAudioSource::OnClose(AudioOutputStream* stream) {
- StopWaitingForPacket();
+ push_source_.OnClose(stream);
}
void AudioRendererHost::IPCAudioSource::OnError(AudioOutputStream* stream,
@@ -230,55 +233,60 @@ void AudioRendererHost::IPCAudioSource::OnError(AudioOutputStream* stream,
host_->DestroySource(this);
}
-void AudioRendererHost::IPCAudioSource::NotifyPacketReady(size_t packet_size) {
- if (packet_size > packet_size_) {
- // If reported size is greater than capacity of the shared memory, close the
- // stream.
- host_->SendErrorMessage(route_id_, stream_id_, 0);
- // We don't need to do packet_read_event_.Signal() here because the
- // contained stream should be closed by the following call and OnClose will
- // be received.
- host_->DestroySource(this);
- return;
+void AudioRendererHost::IPCAudioSource::NotifyPacketReady(
+ size_t decoded_packet_size) {
+ bool ok = true;
+ {
+ AutoLock auto_lock(lock_);
+ outstanding_request_ = false;
+ // If reported size is greater than capacity of the shared memory, we have
+ // an error.
+ if (decoded_packet_size <= decoded_packet_size_) {
+ for (size_t i = 0; i < decoded_packet_size; i += hardware_packet_size_) {
+ size_t size = std::min(decoded_packet_size - i, hardware_packet_size_);
+ ok &= push_source_.Write(
+ static_cast<char*>(shared_memory_.memory()) + i, size);
+ if (!ok)
+ break;
+ }
+
+ // Submit packet request if we have written something.
+ if (ok)
+ SubmitPacketRequest(&auto_lock);
+ }
}
- if (state_ == AudioOutputStream::STATE_CREATED) {
- // If we are in a created state, that means we are performing prefetching.
- uint8* packet = new uint8[packet_size];
- memcpy(packet, shared_memory_.memory(), packet_size);
- initial_buffers_.push_back(std::make_pair(packet, packet_size));
- // If there's not enough initial packets prepared, ask more.
- if (initial_buffers_.size() < stream_->GetNumBuffers()) {
- host_->Send(new ViewMsg_RequestAudioPacket(route_id_, stream_id_));
- } else {
- state_ = AudioOutputStream::STATE_STARTED;
- stream_->Start(this);
- host_->Send(new ViewMsg_NotifyAudioStreamStateChanged(
- route_id_, stream_id_, AudioOutputStream::STATE_STARTED, 0));
- }
- } else {
- AutoLock auto_lock(lock_);
- last_packet_size_ = packet_size;
- packet_read_event_.Signal();
+ // We have received a data packet but we didn't finish writing to push source.
+ // There's error an error and we should stop.
+ if (!ok) {
+ NOTREACHED();
+ }
+}
+
+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() + decoded_packet_size_ <=
+ buffer_capacity_)) {
+ outstanding_request_ = true;
+ host_->Send(new ViewMsg_RequestAudioPacket(route_id_, stream_id_));
}
}
-void AudioRendererHost::IPCAudioSource::StopWaitingForPacket() {
- AutoLock auto_lock(lock_);
- stop_providing_packets_ = true;
- last_packet_size_ = 0;
- packet_read_event_.Signal();
+void AudioRendererHost::IPCAudioSource::SubmitPacketRequest(AutoLock* alock) {
+ if (alock) {
+ SubmitPacketRequest_Locked();
+ } else {
+ AutoLock auto_lock(lock_);
+ SubmitPacketRequest_Locked();
+ }
}
-size_t AudioRendererHost::IPCAudioSource::SafeCopyBuffer(
- void* dest, size_t dest_size, const void* src, size_t src_size) {
- if (src_size > dest_size) {
- host_->SendErrorMessage(route_id_, stream_id_, 0);
- host_->DestroySource(this);
- return 0;
- }
- memcpy(dest, src, src_size);
- return src_size;
+void AudioRendererHost::IPCAudioSource::StartBuffering() {
+ SubmitPacketRequest(NULL);
}
//-----------------------------------------------------------------------------
@@ -333,6 +341,7 @@ bool AudioRendererHost::OnMessageReceived(const IPC::Message& message,
IPC_BEGIN_MESSAGE_MAP_EX(AudioRendererHost, message, *message_was_ok)
IPC_MESSAGE_HANDLER(ViewHostMsg_CreateAudioStream, OnCreateStream)
IPC_MESSAGE_HANDLER(ViewHostMsg_StartAudioStream, OnStartStream)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_PauseAudioStream, OnPauseStream)
IPC_MESSAGE_HANDLER(ViewHostMsg_CloseAudioStream, OnCloseStream)
IPC_MESSAGE_HANDLER(ViewHostMsg_NotifyAudioPacketReady, OnNotifyPacketReady)
IPC_MESSAGE_HANDLER(ViewHostMsg_GetAudioVolume, OnGetVolume)
@@ -347,6 +356,7 @@ bool AudioRendererHost::IsAudioRendererHostMessage(
switch (message.type()) {
case ViewHostMsg_CreateAudioStream::ID:
case ViewHostMsg_StartAudioStream::ID:
+ case ViewHostMsg_PauseAudioStream::ID:
case ViewHostMsg_CloseAudioStream::ID:
case ViewHostMsg_NotifyAudioPacketReady::ID:
case ViewHostMsg_GetAudioVolume::ID:
@@ -374,13 +384,16 @@ void AudioRendererHost::OnCreateStream(
params.channels,
params.sample_rate,
params.bits_per_sample,
- params.packet_size);
+ params.packet_size,
+ params.buffer_capacity);
// 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));
+ } else {
+ SendErrorMessage(msg.routing_id(), stream_id, 0);
}
}
@@ -394,6 +407,16 @@ void AudioRendererHost::OnStartStream(const IPC::Message& msg, int stream_id) {
}
}
+void AudioRendererHost::OnPauseStream(const IPC::Message& msg, int stream_id) {
+ DCHECK(MessageLoop::current() == io_loop_);
+ IPCAudioSource* source = Lookup(msg.routing_id(), stream_id);
+ if (source) {
+ source->Pause();
+ } else {
+ SendErrorMessage(msg.routing_id(), stream_id, 0);
+ }
+}
+
void AudioRendererHost::OnCloseStream(const IPC::Message& msg, int stream_id) {
DCHECK(MessageLoop::current() == io_loop_);
IPCAudioSource* source = Lookup(msg.routing_id(), stream_id);
diff --git a/chrome/browser/renderer_host/audio_renderer_host.h b/chrome/browser/renderer_host/audio_renderer_host.h
index 14ed6d2..f330f86 100644
--- a/chrome/browser/renderer_host/audio_renderer_host.h
+++ b/chrome/browser/renderer_host/audio_renderer_host.h
@@ -9,14 +9,6 @@
// AudioOutputStream object when requested by the renderer provided with
// render view id and stream id.
//
-// AudioRendererHost::IPCAudioSource is a container of AudioOutputStream and
-// provide audio packets to the associated AudioOutputStream through IPC. It
-// transforms the pull data model to a push model used for IPC. When asked by
-// AudioOutputStream for an audio packet, it would send a message to renderer,
-// passing a SharedMemoryHandle for filling the buffer.
-// NotifyPacketReady(|route_id|, |stream_id|) would be called when the
-// buffer is filled and ready to be consumed.
-//
// This class is owned by BrowserRenderProcessHost, and instantiated on UI
// thread, but all other operations and method calls (except Destroy()) happens
// in IO thread, so we need to be extra careful about the lifetime of this
@@ -29,6 +21,48 @@
// which essentially post a task of OnDestroyed() on IO thread. Inside
// 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 ] --> [ Started ] --> [ Paused ]
+// ^ |
+// | |
+// `-----------------`
+//
+// Here's an example of a typical IPC dialog for audio:
+//
+// Renderer AudioRendererHost
+// | >>>>>>>>>>> CreateStream >>>>>>>>> |
+// | <<<<<<<<<<<< Created <<<<<<<<<<<<< |
+// | |
+// | <<<<<< RequestAudioPacket <<<<<<<< |
+// | >>>>>>> AudioPacketReady >>>>>>>>> |
+// | ... |
+// | <<<<<< RequestAudioPacket <<<<<<<< |
+// | >>>>>>> AudioPacketReady >>>>>>>>> |
+// | |
+// | >>>>>>>>>>>>> Start >>>>>>>>>>>>>> |
+// | <<<<<<<<<<<< Started <<<<<<<<<<<<< | time
+// | ... |
+// | <<<<<< RequestAudioPacket <<<<<<<< |
+// | >>>>>>> AudioPacketReady >>>>>>>>> |
+// | ... |
+// | >>>>>>>>>>>>> Pause >>>>>>>>>>>>>> |
+// | <<<<<<<<<<<< Paused <<<<<<<<<<<<< |
+// | ... |
+// | >>>>>>>>>>>>> Start >>>>>>>>>>>>>> |
+// | <<<<<<<<<<<< Started <<<<<<<<<<<<< |
+// | ... |
+// | >>>>>>>>>>>>> Close >>>>>>>>>>>>>> |
+// v v
+//
#ifndef CHROME_BROWSER_RENDERER_HOST_AUDIO_RENDERER_HOST_H_
#define CHROME_BROWSER_RENDERER_HOST_AUDIO_RENDERER_HOST_H_
@@ -43,6 +77,7 @@
#include "base/waitable_event.h"
#include "chrome/common/ipc_message.h"
#include "media/audio/audio_output.h"
+#include "media/audio/simple_sources.h"
class AudioManager;
class MessageLoop;
@@ -94,12 +129,19 @@ class AudioRendererHost : public base::RefCountedThreadSafe<AudioRendererHost> {
void OnCreateStream(const IPC::Message& msg, int stream_id,
const ViewHostMsg_Audio_CreateStream& params);
- // Starts the audio output stream. Delegates the start method call to the
- // corresponding IPCAudioSource::Start, ViewMsg_NotifyAudioStreamStateChanged
- // with AudioOutputStream::AUDIO_STREAM_ERROR is sent back to renderer if the
+ // Starts buffering for the audio output stream. Delegates the start method
+ // call to the corresponding IPCAudioSource::Start.
+ // ViewMsg_NotifyAudioStreamStateChanged with
+ // AudioOutputStream::AUDIO_STREAM_ERROR is sent back to renderer if the
// required IPCAudioSource is not found.
void OnStartStream(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.
+ void OnPauseStream(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.
@@ -168,14 +210,18 @@ class AudioRendererHost : public base::RefCountedThreadSafe<AudioRendererHost> {
MessageLoop* io_loop() { return io_loop_; }
- // The container for AudioOutputStream and serves audio packet for it by IPC.
- // This class does nothing more than sending IPC when OnMoreData is called
- // or error is received from the hardware audio thread, it also serves the
- // purpose of containing the audio output stream and associated information.
- // Lifetime of the audio output stream is not controlled by this class.
+ // The container for AudioOutputStream and serves the audio packet received
+ // via IPC.
class IPCAudioSource : public AudioOutputStream::AudioSourceCallback {
public:
// 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.
@@ -186,19 +232,23 @@ class AudioRendererHost : public base::RefCountedThreadSafe<AudioRendererHost> {
int channels, // Number of channels.
int sample_rate, // Sampling frequency/rate.
char bits_per_sample, // Number of bits per sample.
- size_t packet_size // Number of bytes per packet.
+ size_t decoded_packet_size, // Number of bytes per packet.
+ size_t buffer_capacity // Number of bytes in the buffer.
);
~IPCAudioSource();
// Methods to control playback of the stream.
- // Starts the audio output stream. This method does not call to
- // AudioOutputStream::Start immediately, but instead try get enough initial
- // audio packets from the renderer before actual starting. If pre-rolling
- // has completed and the audio output stream was actually called to start
- // ViewMsg_NotifyAudioStreamStateChanged with
- // AudioOutputStream::AUDIO_STREAM_STARTED is sent back to the renderer.
+ // 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 Start();
+ // 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();
+
// Closes the audio output stream. After calling this method all activities
// of the audio output stream are stopped.
void Close();
@@ -232,26 +282,40 @@ class AudioRendererHost : public base::RefCountedThreadSafe<AudioRendererHost> {
int route_id, // Routing ID to RenderView.
int stream_id, // ID of this source.
AudioOutputStream* stream, // Stream associated.
- size_t packet_size); // Size of shared memory
+ size_t hardware_packet_size,
+ size_t decoded_packet_size, // Size of shared memory
// buffer for writing.
- void StopWaitingForPacket();
- size_t SafeCopyBuffer(void* dest, size_t dest_size,
- const void* src, size_t src_size);
+ size_t 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_;
- size_t packet_size_;
+ size_t hardware_packet_size_;
+ size_t decoded_packet_size_;
+ size_t buffer_capacity_;
AudioOutputStream::State state_;
- bool stop_providing_packets_;
base::SharedMemory shared_memory_;
- base::WaitableEvent packet_read_event_;
+ PushSource push_source_;
+ bool outstanding_request_;
+
+ // Protects:
+ // - |outstanding_requests_|
+ // - |push_source_|
Lock lock_;
- size_t last_packet_size_;
- std::deque<std::pair<uint8*, size_t> > initial_buffers_;
};
int process_id_;
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index ee42f58..6ec1566 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -366,8 +366,13 @@ struct ViewHostMsg_Audio_CreateStream {
// Number of bits per sample;
int bits_per_sample;
- // Number of bytes per packet.
+ // Number of bytes per packet. Determines the maximum number of bytes
+ // transported for each audio packet request.
size_t packet_size;
+
+ // Maximum number of bytes of audio packets that should be kept in the browser
+ // process.
+ size_t buffer_capacity;
};
// This message is used for supporting popup menus on Mac OS X using native
@@ -1669,6 +1674,7 @@ struct ParamTraits<ViewHostMsg_Audio_CreateStream> {
WriteParam(m, p.sample_rate);
WriteParam(m, p.bits_per_sample);
WriteParam(m, p.packet_size);
+ WriteParam(m, p.buffer_capacity);
}
static bool Read(const Message* m, void** iter, param_type* p) {
return
@@ -1676,7 +1682,8 @@ struct ParamTraits<ViewHostMsg_Audio_CreateStream> {
ReadParam(m, iter, &p->channels) &&
ReadParam(m, iter, &p->sample_rate) &&
ReadParam(m, iter, &p->bits_per_sample) &&
- ReadParam(m, iter, &p->packet_size);
+ ReadParam(m, iter, &p->packet_size) &&
+ ReadParam(m, iter, &p->buffer_capacity);
}
static void Log(const param_type& p, std::wstring* l) {
l->append(L"<ViewHostMsg_Audio_CreateStream>(");
@@ -1690,6 +1697,8 @@ struct ParamTraits<ViewHostMsg_Audio_CreateStream> {
l->append(L", ");
LogParam(p.packet_size, l);
l->append(L")");
+ LogParam(p.buffer_capacity, l);
+ l->append(L")");
}
};
diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h
index dc33dd1..2b47570 100644
--- a/chrome/common/render_messages_internal.h
+++ b/chrome/common/render_messages_internal.h
@@ -1309,10 +1309,14 @@ IPC_BEGIN_MESSAGES(ViewHost)
int /* stream_id */,
size_t /* packet size */)
- // Start playing the audio stream specified by (render_view_id, stream_id).
+ // Start buffering the audio stream specified by (render_view_id, stream_id).
IPC_MESSAGE_ROUTED1(ViewHostMsg_StartAudioStream,
int /* stream_id */)
+ // Pause the audio stream specified by (render_view_id, stream_id).
+ IPC_MESSAGE_ROUTED1(ViewHostMsg_PauseAudioStream,
+ int /* stream_id */)
+
// Close an audio stream specified by (render_view_id, stream_id).
IPC_MESSAGE_ROUTED1(ViewHostMsg_CloseAudioStream,
int /* stream_id */)
diff --git a/chrome/renderer/media/audio_renderer_impl.cc b/chrome/renderer/media/audio_renderer_impl.cc
index 661e933..9dd365c 100644
--- a/chrome/renderer/media/audio_renderer_impl.cc
+++ b/chrome/renderer/media/audio_renderer_impl.cc
@@ -9,9 +9,22 @@
#include "chrome/renderer/render_thread.h"
#include "media/base/filter_host.h"
-// We'll try to fill 8192 samples per buffer, which is roughly ~194ms of audio
-// data for a 44.1kHz audio source.
-static const size_t kSamplesPerBuffer = 8192;
+namespace {
+
+// We will try to fill 200 ms worth of audio samples in each packet. A round
+// trip latency for IPC messages are typically 10 ms, this should give us
+// plenty of time to avoid clicks.
+const int kMillisecondsPerPacket = 200;
+
+// We have at most 3 packets in browser, i.e. 600 ms. This is a reasonable
+// amount to avoid clicks.
+const int kPacketsInBuffer = 3;
+
+// We want to preroll 400 milliseconds before starting to play. Again, 400 ms
+// of audio data should give us enough time to get more from the renderer.
+const int kMillisecondsPreroll = 400;
+
+} // namespace
AudioRendererImpl::AudioRendererImpl(AudioMessageFilter* filter)
: AudioRendererBase(kDefaultMaxQueueSize),
@@ -21,8 +34,10 @@ AudioRendererImpl::AudioRendererImpl(AudioMessageFilter* filter)
shared_memory_size_(0),
io_loop_(filter->message_loop()),
stopped_(false),
+ pending_request_(false),
playback_rate_(0.0f),
- packet_request_event_(true, false) {
+ prerolling_(true),
+ preroll_bytes_(0) {
DCHECK(io_loop_);
}
@@ -47,11 +62,17 @@ bool AudioRendererImpl::OnInitialize(const media::MediaFormat& media_format) {
}
// Create the audio output stream in browser process.
- size_t packet_size = kSamplesPerBuffer * channels * sample_bits / 8;
+ size_t bytes_per_second = sample_rate * channels * sample_bits / 8;
+ size_t packet_size = bytes_per_second * kMillisecondsPerPacket / 1000;
+ size_t buffer_capacity = packet_size * kPacketsInBuffer;
+
+ // Calculate the amount for prerolling.
+ preroll_bytes_ = bytes_per_second * kMillisecondsPreroll / 1000;
+
io_loop_->PostTask(FROM_HERE,
NewRunnableMethod(this, &AudioRendererImpl::OnCreateStream,
AudioManager::AUDIO_PCM_LINEAR, channels, sample_rate, sample_bits,
- packet_size));
+ packet_size, buffer_capacity));
return true;
}
@@ -69,18 +90,44 @@ void AudioRendererImpl::OnReadComplete(media::Buffer* buffer_in) {
AutoLock auto_lock(lock_);
if (stopped_)
return;
+
+ // TODO(hclam): handle end of stream here.
+
// Use the base class to queue the buffer.
AudioRendererBase::OnReadComplete(buffer_in);
+
// Post a task to render thread to notify a packet reception.
io_loop_->PostTask(FROM_HERE,
NewRunnableMethod(this, &AudioRendererImpl::OnNotifyPacketReady));
}
void AudioRendererImpl::SetPlaybackRate(float rate) {
- // TODO(hclam): This is silly. We should use a playback rate of != 1.0 to
- // stop the audio stream. This does not work right now, so we just check
- // for this in OnNotifyPacketReady().
+ DCHECK(rate >= 0.0f);
+
+ // We have two cases here:
+ // Play: playback_rate_ == 0.0 && rate != 0.0
+ // Pause: playback_rate_ != 0.0 && rate == 0.0
+ AutoLock auto_lock(lock_);
+ if (playback_rate_ == 0.0f && rate != 0.0f) {
+ // Play is a bit tricky, we can only play if we have done prerolling.
+ // TODO(hclam): I should check for end of streams status here.
+ if (!prerolling_)
+ io_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &AudioRendererImpl::OnPlay));
+ } else if (playback_rate_ != 0.0f && rate == 0.0f) {
+ // Pause is easy, we can always pause.
+ io_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &AudioRendererImpl::OnPause));
+ }
playback_rate_ = rate;
+
+ // If we are playing, give a kick to try fulfilling the packet request as
+ // the previous packet request may be stalled by a pause.
+ if (rate > 0.0f) {
+ io_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioRendererImpl::OnNotifyPacketReady));
+ }
}
void AudioRendererImpl::SetVolume(float volume) {
@@ -104,14 +151,16 @@ void AudioRendererImpl::OnCreated(base::SharedMemoryHandle handle,
shared_memory_.reset(new base::SharedMemory(handle, false));
shared_memory_->Map(length);
shared_memory_size_ = length;
-
- filter_->Send(new ViewHostMsg_StartAudioStream(0, stream_id_));
}
void AudioRendererImpl::OnRequestPacket() {
DCHECK(MessageLoop::current() == io_loop_);
- packet_request_event_.Signal();
+ {
+ AutoLock auto_lock(lock_);
+ DCHECK(!pending_request_);
+ pending_request_ = true;
+ }
// Try to fill in the fulfil the packet request.
OnNotifyPacketReady();
@@ -146,7 +195,7 @@ void AudioRendererImpl::OnVolume(double left, double right) {
void AudioRendererImpl::OnCreateStream(
AudioManager::Format format, int channels, int sample_rate,
- int bits_per_sample, size_t packet_size) {
+ int bits_per_sample, size_t packet_size, size_t buffer_capacity) {
DCHECK(MessageLoop::current() == io_loop_);
AutoLock auto_lock(lock_);
@@ -163,10 +212,23 @@ void AudioRendererImpl::OnCreateStream(
params.sample_rate = sample_rate;
params.bits_per_sample = bits_per_sample;
params.packet_size = packet_size;
+ params.buffer_capacity = buffer_capacity;
filter_->Send(new ViewHostMsg_CreateAudioStream(0, stream_id_, params));
}
+void AudioRendererImpl::OnPlay() {
+ DCHECK(MessageLoop::current() == io_loop_);
+
+ filter_->Send(new ViewHostMsg_StartAudioStream(0, stream_id_));
+}
+
+void AudioRendererImpl::OnPause() {
+ DCHECK(MessageLoop::current() == io_loop_);
+
+ filter_->Send(new ViewHostMsg_PauseAudioStream(0, stream_id_));
+}
+
void AudioRendererImpl::OnDestroy() {
DCHECK(MessageLoop::current() == io_loop_);
@@ -189,22 +251,28 @@ void AudioRendererImpl::OnNotifyPacketReady() {
AutoLock auto_lock(lock_);
if (stopped_)
return;
- if (packet_request_event_.IsSignaled()) {
- size_t filled = 0;
+ if (pending_request_ && playback_rate_ > 0.0f) {
DCHECK(shared_memory_.get());
- // TODO(hclam): This is a hack. The stream should be stopped.
- if (playback_rate_ > 0.0f) {
- filled = FillBuffer(static_cast<uint8*>(shared_memory_->memory()),
- shared_memory_size_, playback_rate_);
- } else {
- memset(shared_memory_->memory(), 0, shared_memory_size_);
- filled = shared_memory_size_;
- }
+ size_t filled = FillBuffer(static_cast<uint8*>(shared_memory_->memory()),
+ shared_memory_size_,
+ playback_rate_);
+ // TODO(hclam): we should try to fill in the buffer as much as possible.
if (filled > 0) {
- packet_request_event_.Reset();
+ pending_request_ = false;
// Then tell browser process we are done filling into the buffer.
filter_->Send(
new ViewHostMsg_NotifyAudioPacketReady(0, stream_id_, filled));
+
+ if (prerolling_) {
+ // We have completed prerolling.
+ if (filled > preroll_bytes_) {
+ prerolling_ = false;
+ preroll_bytes_ = 0;
+ filter_->Send(new ViewHostMsg_StartAudioStream(0, stream_id_));
+ } else {
+ preroll_bytes_ -= filled;
+ }
+ }
}
}
}
diff --git a/chrome/renderer/media/audio_renderer_impl.h b/chrome/renderer/media/audio_renderer_impl.h
index 71a00fc..6a24dfc 100644
--- a/chrome/renderer/media/audio_renderer_impl.h
+++ b/chrome/renderer/media/audio_renderer_impl.h
@@ -150,7 +150,9 @@ class AudioRendererImpl : public media::AudioRendererBase,
// sends IPC messages on that thread.
void OnCreateStream(AudioManager::Format format, int channels,
int sample_rate, int bits_per_sample,
- size_t packet_size);
+ size_t packet_size, size_t buffer_capacity);
+ void OnPlay();
+ void OnPause();
void OnSetVolume(double left, double right);
void OnNotifyPacketReady();
void OnDestroy();
@@ -164,13 +166,21 @@ class AudioRendererImpl : public media::AudioRendererBase,
scoped_ptr<base::SharedMemory> shared_memory_;
size_t shared_memory_size_;
- // Message loop for the io thread.
+ // Message loop for the IO thread.
MessageLoop* io_loop_;
+ // Protects:
+ // - |playback_rate_|
+ // - |stopped_|
+ // - |pending_request_|
Lock lock_;
bool stopped_;
+ bool pending_request_;
float playback_rate_;
- base::WaitableEvent packet_request_event_;
+
+ // State variables for prerolling.
+ bool prerolling_;
+ size_t preroll_bytes_;
DISALLOW_COPY_AND_ASSIGN(AudioRendererImpl);
};
diff --git a/media/audio/win/audio_output_win_unittest.cc b/media/audio/win/audio_output_win_unittest.cc
index 5e58798..c71d944 100644
--- a/media/audio/win/audio_output_win_unittest.cc
+++ b/media/audio/win/audio_output_win_unittest.cc
@@ -444,3 +444,42 @@ TEST(WinAudioTest, PushSourceFile16KHz) {
oas->Stop();
oas->Close();
}
+
+// This test is to make sure an AudioOutputStream can be started after it was
+// stopped. You will here two 1.5 seconds wave signal separated by 0.5 seconds
+// of silence.
+TEST(WinAudioTest, PCMWaveStreamPlayTwice200HzTone44Kss) {
+ if (IsRunningHeadless())
+ return;
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ ASSERT_TRUE(NULL != audio_man);
+ if (!audio_man->HasAudioDevices())
+ return;
+ AudioOutputStream* oas =
+ audio_man->MakeAudioStream(AudioManager::AUDIO_PCM_LINEAR, 1,
+ AudioManager::kAudioCDSampleRate, 16);
+ ASSERT_TRUE(NULL != oas);
+
+ SineWaveAudioSource source(SineWaveAudioSource::FORMAT_16BIT_LINEAR_PCM, 1,
+ 200.0, AudioManager::kAudioCDSampleRate);
+ size_t bytes_100_ms = (AudioManager::kAudioCDSampleRate / 10) * 2;
+
+ EXPECT_TRUE(oas->Open(bytes_100_ms));
+ oas->SetVolume(1.0, 1.0);
+
+ // Play the wave for 1.5 seconds.
+ oas->Start(&source);
+ ::Sleep(1500);
+ oas->Stop();
+
+ // Sleep to give silence after stopping the AudioOutputStream.
+ ::Sleep(500);
+
+ // Start again and play for 1.5 seconds.
+ oas->Start(&source);
+ ::Sleep(1500);
+ oas->Stop();
+
+ oas->Close();
+}
+
diff --git a/media/audio/win/waveout_output_win.cc b/media/audio/win/waveout_output_win.cc
index 2f5d8ab..9828bd8 100644
--- a/media/audio/win/waveout_output_win.cc
+++ b/media/audio/win/waveout_output_win.cc
@@ -171,7 +171,9 @@ void PCMWaveOutAudioOutputStream::Stop() {
if (res != MMSYSERR_NOERROR) {
state_ = PCMA_PLAYING;
HandleError(res);
+ return;
}
+ state_ = PCMA_READY;
}
// We can Close in any state except that trying to close a stream that is