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