diff options
-rw-r--r-- | chrome/browser/renderer_host/audio_renderer_host.cc | 275 | ||||
-rw-r--r-- | chrome/browser/renderer_host/audio_renderer_host.h | 128 | ||||
-rw-r--r-- | chrome/common/render_messages.h | 13 | ||||
-rw-r--r-- | chrome/common/render_messages_internal.h | 6 | ||||
-rw-r--r-- | chrome/renderer/media/audio_renderer_impl.cc | 116 | ||||
-rw-r--r-- | chrome/renderer/media/audio_renderer_impl.h | 16 | ||||
-rw-r--r-- | media/audio/win/audio_output_win_unittest.cc | 39 | ||||
-rw-r--r-- | media/audio/win/waveout_output_win.cc | 2 |
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 |