diff options
-rw-r--r-- | chrome/browser/renderer_host/audio_renderer_host.cc | 25 | ||||
-rw-r--r-- | chrome/browser/renderer_host/audio_renderer_host.h | 9 | ||||
-rw-r--r-- | chrome/common/render_messages_internal.h | 6 | ||||
-rw-r--r-- | chrome/renderer/audio_message_filter.cc | 7 | ||||
-rw-r--r-- | chrome/renderer/audio_message_filter.h | 6 | ||||
-rw-r--r-- | chrome/renderer/audio_message_filter_unittest.cc | 406 | ||||
-rw-r--r-- | chrome/renderer/media/audio_renderer_impl.cc | 60 | ||||
-rw-r--r-- | chrome/renderer/media/audio_renderer_impl.h | 34 | ||||
-rw-r--r-- | media/filters/audio_renderer_base.cc | 17 | ||||
-rw-r--r-- | media/filters/audio_renderer_base.h | 14 | ||||
-rw-r--r-- | media/filters/audio_renderer_impl.cc | 3 | ||||
-rw-r--r-- | media/filters/null_audio_renderer.cc | 5 |
12 files changed, 371 insertions, 221 deletions
diff --git a/chrome/browser/renderer_host/audio_renderer_host.cc b/chrome/browser/renderer_host/audio_renderer_host.cc index f07d8e5..1d2bf07 100644 --- a/chrome/browser/renderer_host/audio_renderer_host.cc +++ b/chrome/browser/renderer_host/audio_renderer_host.cc @@ -216,6 +216,7 @@ size_t AudioRendererHost::IPCAudioSource::OnMoreData(AudioOutputStream* stream, size_t size = push_source_.OnMoreData(stream, dest, max_size); { AutoLock auto_lock(lock_); + last_copied_bytes_ = size; SubmitPacketRequest(&auto_lock); } return size; @@ -239,6 +240,11 @@ void AudioRendererHost::IPCAudioSource::NotifyPacketReady( { AutoLock auto_lock(lock_); outstanding_request_ = false; +#ifdef IPC_MESSAGE_LOG_ENABLED + if (IPC::Logging::current()->Enabled()) { + RecordRoundTripLatency(base::Time::Now() - outstanding_request_time_); + } +#endif // If reported size is greater than capacity of the shared memory, we have // an error. if (decoded_packet_size <= decoded_packet_size_) { @@ -272,7 +278,24 @@ void AudioRendererHost::IPCAudioSource::SubmitPacketRequest_Locked() { (push_source_.UnProcessedBytes() + decoded_packet_size_ <= buffer_capacity_)) { outstanding_request_ = true; - host_->Send(new ViewMsg_RequestAudioPacket(route_id_, stream_id_)); + outstanding_request_time_ = base::Time::Now(); + + // This variable keeps track of the total amount of bytes buffered for + // the associated AudioOutputStream. This value should consist of bytes + // buffered in AudioOutputStream and those kept inside |push_source_|. + // TODO(hclam): since we have no information about the amount of buffered + // bytes in the hardware buffer in AudioOutputStream, we make our best + // guess by using the amount of the last copy. This should be a good guess + // for Windows since it does double buffering but we shold change this + // when AudioOutputStream has the API to query remaining buffer. + size_t buffered_bytes = last_copied_bytes_ + + push_source_.UnProcessedBytes(); + host_->Send( + new ViewMsg_RequestAudioPacket( + route_id_, + stream_id_, + buffered_bytes, + outstanding_request_time_.ToInternalValue())); } } diff --git a/chrome/browser/renderer_host/audio_renderer_host.h b/chrome/browser/renderer_host/audio_renderer_host.h index f330f86..961917e 100644 --- a/chrome/browser/renderer_host/audio_renderer_host.h +++ b/chrome/browser/renderer_host/audio_renderer_host.h @@ -25,7 +25,7 @@ // 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 ] <--------. @@ -310,10 +310,17 @@ class AudioRendererHost : public base::RefCountedThreadSafe<AudioRendererHost> { AudioOutputStream::State state_; base::SharedMemory shared_memory_; PushSource push_source_; + + // Flag that indicates there is an outstanding request. bool outstanding_request_; + base::Time outstanding_request_time_; + + // Number of bytes copied in the last OnMoreData call. + size_t last_copied_bytes_; // Protects: // - |outstanding_requests_| + // - |last_copied_bytes_| // - |push_source_| Lock lock_; }; diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h index f7ba56c..7de2db7 100644 --- a/chrome/common/render_messages_internal.h +++ b/chrome/common/render_messages_internal.h @@ -508,8 +508,10 @@ IPC_BEGIN_MESSAGES(View) bool /* Whether it is visible */) // Sent by AudioRendererHost to renderer to request an audio packet. - IPC_MESSAGE_ROUTED1(ViewMsg_RequestAudioPacket, - int /* stream id */) + IPC_MESSAGE_ROUTED3(ViewMsg_RequestAudioPacket, + int /* stream id */, + size_t /* bytes in buffer */, + int64 /* message timestamp */) // Tell the renderer process that the audio stream has been created, renderer // process would be given a ShareMemoryHandle that it should write to from diff --git a/chrome/renderer/audio_message_filter.cc b/chrome/renderer/audio_message_filter.cc index 944118d..0714b71 100644 --- a/chrome/renderer/audio_message_filter.cc +++ b/chrome/renderer/audio_message_filter.cc @@ -74,7 +74,9 @@ void AudioMessageFilter::OnChannelClosing() { } void AudioMessageFilter::OnRequestPacket(const IPC::Message& msg, - int stream_id) { + int stream_id, + size_t bytes_in_buffer, + int64 message_timestamp) { Delegate* delegate = delegates_.Lookup(stream_id); if (!delegate) { DLOG(WARNING) << "Got audio packet request for a non-existent or removed" @@ -90,7 +92,8 @@ void AudioMessageFilter::OnRequestPacket(const IPC::Message& msg, } #endif - delegate->OnRequestPacket(); + delegate->OnRequestPacket(bytes_in_buffer, + base::Time::FromInternalValue(message_timestamp)); #ifdef IPC_MESSAGE_LOG_ENABLED if (logger->Enabled()) { diff --git a/chrome/renderer/audio_message_filter.h b/chrome/renderer/audio_message_filter.h index 6e90edb..7626b3c 100644 --- a/chrome/renderer/audio_message_filter.h +++ b/chrome/renderer/audio_message_filter.h @@ -21,7 +21,8 @@ class AudioMessageFilter : public IPC::ChannelProxy::MessageFilter { class Delegate { public: // Called when an audio packet is requested from the browser process. - virtual void OnRequestPacket() = 0; + virtual void OnRequestPacket(size_t bytes_in_buffer, + const base::Time& message_timestamp) = 0; // Called when state of an audio stream has changed in the browser process. virtual void OnStateChanged(AudioOutputStream::State state, int info) = 0; @@ -59,7 +60,8 @@ class AudioMessageFilter : public IPC::ChannelProxy::MessageFilter { virtual void OnChannelClosing(); // Received when browser process wants more audio packet. - void OnRequestPacket(const IPC::Message& msg, int stream_id); + void OnRequestPacket(const IPC::Message& msg, int stream_id, + size_t bytes_in_buffer, int64 message_timestamp); // Received when browser process has created an audio output stream. void OnStreamCreated(int stream_id, base::SharedMemoryHandle handle, diff --git a/chrome/renderer/audio_message_filter_unittest.cc b/chrome/renderer/audio_message_filter_unittest.cc index bc9fc8e..d09a406 100644 --- a/chrome/renderer/audio_message_filter_unittest.cc +++ b/chrome/renderer/audio_message_filter_unittest.cc @@ -1,193 +1,213 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/logging.h"
-#include "base/message_loop.h"
-#include "chrome/common/render_messages.h"
-#include "chrome/renderer/audio_message_filter.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-
-class MockAudioDelegate : public AudioMessageFilter::Delegate {
- public:
- MockAudioDelegate() {
- Reset();
- }
-
- virtual void OnRequestPacket() {
- request_packet_received_ = true;
- }
-
- virtual void OnStateChanged(AudioOutputStream::State state, int info) {
- state_changed_received_ = true;
- state_ = state;
- info_ = info;
- }
-
- virtual void OnCreated(base::SharedMemoryHandle handle, size_t length) {
- created_received_ = true;
- handle_ = handle;
- length_ = length;
- }
-
- virtual void OnVolume(double left, double right) {
- volume_received_ = true;
- left_ = left;
- right_ = right;
- }
-
- void Reset() {
- request_packet_received_ = false;
-
- state_changed_received_ = false;
- state_ = AudioOutputStream::STATE_ERROR;
- info_ = 0;
-
- created_received_ = false;
- handle_ = base::SharedMemory::NULLHandle();
- length_ = 0;
-
- volume_received_ = false;
- left_ = 0;
- right_ = 0;
- }
-
- bool request_packet_received() { return request_packet_received_; }
-
- bool state_changed_received() { return state_changed_received_; }
- AudioOutputStream::State state() { return state_; }
- int info() { return info_; }
-
- bool created_received() { return created_received_; }
- base::SharedMemoryHandle handle() { return handle_; }
- size_t length() { return length_; }
-
- bool volume_received() { return volume_received_; }
- double left() { return left_; }
- double right() { return right_; }
-
- private:
- bool request_packet_received_;
-
- bool state_changed_received_;
- AudioOutputStream::State state_;
- int info_;
-
- bool created_received_;
- base::SharedMemoryHandle handle_;
- size_t length_;
-
- bool volume_received_;
- double left_;
- double right_;
-
- DISALLOW_COPY_AND_ASSIGN(MockAudioDelegate);
-};
-
-} // namespace
-
-TEST(AudioMessageFilterTest, Basic) {
- MessageLoop message_loop(MessageLoop::TYPE_IO);
-
- const int kRouteId = 0;
- scoped_refptr<AudioMessageFilter> filter = new AudioMessageFilter(kRouteId);
-
- MockAudioDelegate delegate;
- int stream_id = filter->AddDelegate(&delegate);
-
- // ViewMsg_RequestAudioPacket
- EXPECT_FALSE(delegate.request_packet_received());
- filter->OnMessageReceived(ViewMsg_RequestAudioPacket(kRouteId, stream_id));
- EXPECT_TRUE(delegate.request_packet_received());
- delegate.Reset();
-
- // ViewMsg_NotifyAudioStreamStateChanged
- const AudioOutputStream::State kState = AudioOutputStream::STATE_STARTED;
- const int kStateInfo = 100;
- EXPECT_FALSE(delegate.state_changed_received());
- filter->OnMessageReceived(
- ViewMsg_NotifyAudioStreamStateChanged(kRouteId,
- stream_id,
- kState,
- kStateInfo));
- EXPECT_TRUE(delegate.state_changed_received());
- EXPECT_TRUE(kState == delegate.state());
- EXPECT_EQ(kStateInfo, delegate.info());
- delegate.Reset();
-
- // ViewMsg_NotifyAudioStreamCreated
- const size_t kLength = 1024;
- EXPECT_FALSE(delegate.created_received());
- filter->OnMessageReceived(
- ViewMsg_NotifyAudioStreamCreated(kRouteId,
- stream_id,
- base::SharedMemory::NULLHandle(),
- kLength));
- EXPECT_TRUE(delegate.created_received());
- EXPECT_FALSE(base::SharedMemory::IsHandleValid(delegate.handle()));
- EXPECT_EQ(kLength, delegate.length());
- delegate.Reset();
-
- // ViewMsg_NotifyAudioStreamVolume
- const double kLeftVolume = 1.0;
- const double kRightVolume = 2.0;
- EXPECT_FALSE(delegate.volume_received());
- filter->OnMessageReceived(
- ViewMsg_NotifyAudioStreamVolume(kRouteId, stream_id,
- kLeftVolume, kRightVolume));
- EXPECT_TRUE(delegate.volume_received());
- EXPECT_EQ(kLeftVolume, delegate.left());
- EXPECT_EQ(kRightVolume, delegate.right());
- delegate.Reset();
-
- message_loop.RunAllPending();
-}
-
-TEST(AudioMessageFilterTest, Delegates) {
- MessageLoop message_loop(MessageLoop::TYPE_IO);
-
- const int kRouteId = 0;
- scoped_refptr<AudioMessageFilter> filter = new AudioMessageFilter(kRouteId);
-
- MockAudioDelegate delegate1;
- MockAudioDelegate delegate2;
-
- int stream_id1 = filter->AddDelegate(&delegate1);
- int stream_id2 = filter->AddDelegate(&delegate2);
-
- // Send an IPC message. Make sure the correct delegate gets called.
- EXPECT_FALSE(delegate1.request_packet_received());
- EXPECT_FALSE(delegate2.request_packet_received());
- filter->OnMessageReceived(ViewMsg_RequestAudioPacket(kRouteId, stream_id1));
- EXPECT_TRUE(delegate1.request_packet_received());
- EXPECT_FALSE(delegate2.request_packet_received());
- delegate1.Reset();
-
- EXPECT_FALSE(delegate1.request_packet_received());
- EXPECT_FALSE(delegate2.request_packet_received());
- filter->OnMessageReceived(ViewMsg_RequestAudioPacket(kRouteId, stream_id2));
- EXPECT_FALSE(delegate1.request_packet_received());
- EXPECT_TRUE(delegate2.request_packet_received());
- delegate2.Reset();
-
- // Send a message of a different route id, a message is not received.
- EXPECT_FALSE(delegate1.request_packet_received());
- filter->OnMessageReceived(ViewMsg_RequestAudioPacket(kRouteId + 1,
- stream_id1));
- EXPECT_FALSE(delegate1.request_packet_received());
-
- // Remove the delegates. Make sure they won't get called.
- filter->RemoveDelegate(stream_id1);
- EXPECT_FALSE(delegate1.request_packet_received());
- filter->OnMessageReceived(ViewMsg_RequestAudioPacket(kRouteId, stream_id1));
- EXPECT_FALSE(delegate1.request_packet_received());
-
- filter->RemoveDelegate(stream_id2);
- EXPECT_FALSE(delegate2.request_packet_received());
- filter->OnMessageReceived(ViewMsg_RequestAudioPacket(kRouteId, stream_id2));
- EXPECT_FALSE(delegate2.request_packet_received());
-
- message_loop.RunAllPending();
-}
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "base/message_loop.h" +#include "chrome/common/render_messages.h" +#include "chrome/renderer/audio_message_filter.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class MockAudioDelegate : public AudioMessageFilter::Delegate { + public: + MockAudioDelegate() { + Reset(); + } + + virtual void OnRequestPacket(size_t bytes_in_buffer, + const base::Time& message_timestamp) { + request_packet_received_ = true; + bytes_in_buffer_ = bytes_in_buffer; + message_timestamp_ = message_timestamp; + } + + virtual void OnStateChanged(AudioOutputStream::State state, int info) { + state_changed_received_ = true; + state_ = state; + info_ = info; + } + + virtual void OnCreated(base::SharedMemoryHandle handle, size_t length) { + created_received_ = true; + handle_ = handle; + length_ = length; + } + + virtual void OnVolume(double left, double right) { + volume_received_ = true; + left_ = left; + right_ = right; + } + + void Reset() { + request_packet_received_ = false; + bytes_in_buffer_ = 0; + message_timestamp_ = base::Time(); + + state_changed_received_ = false; + state_ = AudioOutputStream::STATE_ERROR; + info_ = 0; + + created_received_ = false; + handle_ = base::SharedMemory::NULLHandle(); + length_ = 0; + + volume_received_ = false; + left_ = 0; + right_ = 0; + } + + bool request_packet_received() { return request_packet_received_; } + size_t bytes_in_buffer() { return bytes_in_buffer_; } + const base::Time& message_timestamp() { return message_timestamp_; } + + bool state_changed_received() { return state_changed_received_; } + AudioOutputStream::State state() { return state_; } + int info() { return info_; } + + bool created_received() { return created_received_; } + base::SharedMemoryHandle handle() { return handle_; } + size_t length() { return length_; } + + bool volume_received() { return volume_received_; } + double left() { return left_; } + double right() { return right_; } + + private: + bool request_packet_received_; + size_t bytes_in_buffer_; + base::Time message_timestamp_; + + bool state_changed_received_; + AudioOutputStream::State state_; + int info_; + + bool created_received_; + base::SharedMemoryHandle handle_; + size_t length_; + + bool volume_received_; + double left_; + double right_; + + DISALLOW_COPY_AND_ASSIGN(MockAudioDelegate); +}; + +} // namespace + +TEST(AudioMessageFilterTest, Basic) { + MessageLoop message_loop(MessageLoop::TYPE_IO); + + const int kRouteId = 0; + scoped_refptr<AudioMessageFilter> filter = new AudioMessageFilter(kRouteId); + + MockAudioDelegate delegate; + int stream_id = filter->AddDelegate(&delegate); + + // ViewMsg_RequestAudioPacket + const size_t kSizeInBuffer = 1024; + const int64 kMessageTimestamp = 99; + EXPECT_FALSE(delegate.request_packet_received()); + filter->OnMessageReceived(ViewMsg_RequestAudioPacket(kRouteId, + stream_id, + kSizeInBuffer, + kMessageTimestamp)); + EXPECT_TRUE(delegate.request_packet_received()); + EXPECT_EQ(kSizeInBuffer, delegate.bytes_in_buffer()); + EXPECT_EQ(kMessageTimestamp, delegate.message_timestamp().ToInternalValue()); + delegate.Reset(); + + // ViewMsg_NotifyAudioStreamStateChanged + const AudioOutputStream::State kState = AudioOutputStream::STATE_STARTED; + const int kStateInfo = 100; + EXPECT_FALSE(delegate.state_changed_received()); + filter->OnMessageReceived( + ViewMsg_NotifyAudioStreamStateChanged(kRouteId, + stream_id, + kState, + kStateInfo)); + EXPECT_TRUE(delegate.state_changed_received()); + EXPECT_TRUE(kState == delegate.state()); + EXPECT_EQ(kStateInfo, delegate.info()); + delegate.Reset(); + + // ViewMsg_NotifyAudioStreamCreated + const size_t kLength = 1024; + EXPECT_FALSE(delegate.created_received()); + filter->OnMessageReceived( + ViewMsg_NotifyAudioStreamCreated(kRouteId, + stream_id, + base::SharedMemory::NULLHandle(), + kLength)); + EXPECT_TRUE(delegate.created_received()); + EXPECT_FALSE(base::SharedMemory::IsHandleValid(delegate.handle())); + EXPECT_EQ(kLength, delegate.length()); + delegate.Reset(); + + // ViewMsg_NotifyAudioStreamVolume + const double kLeftVolume = 1.0; + const double kRightVolume = 2.0; + EXPECT_FALSE(delegate.volume_received()); + filter->OnMessageReceived( + ViewMsg_NotifyAudioStreamVolume(kRouteId, stream_id, + kLeftVolume, kRightVolume)); + EXPECT_TRUE(delegate.volume_received()); + EXPECT_EQ(kLeftVolume, delegate.left()); + EXPECT_EQ(kRightVolume, delegate.right()); + delegate.Reset(); + + message_loop.RunAllPending(); +} + +TEST(AudioMessageFilterTest, Delegates) { + MessageLoop message_loop(MessageLoop::TYPE_IO); + + const int kRouteId = 0; + scoped_refptr<AudioMessageFilter> filter = new AudioMessageFilter(kRouteId); + + MockAudioDelegate delegate1; + MockAudioDelegate delegate2; + + int stream_id1 = filter->AddDelegate(&delegate1); + int stream_id2 = filter->AddDelegate(&delegate2); + + // Send an IPC message. Make sure the correct delegate gets called. + EXPECT_FALSE(delegate1.request_packet_received()); + EXPECT_FALSE(delegate2.request_packet_received()); + filter->OnMessageReceived( + ViewMsg_RequestAudioPacket(kRouteId, stream_id1, 0, 0)); + EXPECT_TRUE(delegate1.request_packet_received()); + EXPECT_FALSE(delegate2.request_packet_received()); + delegate1.Reset(); + + EXPECT_FALSE(delegate1.request_packet_received()); + EXPECT_FALSE(delegate2.request_packet_received()); + filter->OnMessageReceived( + ViewMsg_RequestAudioPacket(kRouteId, stream_id2, 0, 0)); + EXPECT_FALSE(delegate1.request_packet_received()); + EXPECT_TRUE(delegate2.request_packet_received()); + delegate2.Reset(); + + // Send a message of a different route id, a message is not received. + EXPECT_FALSE(delegate1.request_packet_received()); + filter->OnMessageReceived( + ViewMsg_RequestAudioPacket(kRouteId + 1, stream_id1, 0, 0)); + EXPECT_FALSE(delegate1.request_packet_received()); + + // Remove the delegates. Make sure they won't get called. + filter->RemoveDelegate(stream_id1); + EXPECT_FALSE(delegate1.request_packet_received()); + filter->OnMessageReceived( + ViewMsg_RequestAudioPacket(kRouteId, stream_id1, 0, 0)); + EXPECT_FALSE(delegate1.request_packet_received()); + + filter->RemoveDelegate(stream_id2); + EXPECT_FALSE(delegate2.request_packet_received()); + filter->OnMessageReceived( + ViewMsg_RequestAudioPacket(kRouteId, stream_id2, 0, 0)); + EXPECT_FALSE(delegate2.request_packet_received()); + + message_loop.RunAllPending(); +} diff --git a/chrome/renderer/media/audio_renderer_impl.cc b/chrome/renderer/media/audio_renderer_impl.cc index 9dd365c..73f2320 100644 --- a/chrome/renderer/media/audio_renderer_impl.cc +++ b/chrome/renderer/media/audio_renderer_impl.cc @@ -28,14 +28,18 @@ const int kMillisecondsPreroll = 400; AudioRendererImpl::AudioRendererImpl(AudioMessageFilter* filter) : AudioRendererBase(kDefaultMaxQueueSize), + channels_(0), + sample_rate_(0), + sample_bits_(0), + bytes_per_second_(0), filter_(filter), stream_id_(0), shared_memory_(NULL), shared_memory_size_(0), io_loop_(filter->message_loop()), stopped_(false), - pending_request_(false), playback_rate_(0.0f), + pending_request_(false), prerolling_(true), preroll_bytes_(0) { DCHECK(io_loop_); @@ -44,6 +48,14 @@ AudioRendererImpl::AudioRendererImpl(AudioMessageFilter* filter) AudioRendererImpl::~AudioRendererImpl() { } +base::TimeDelta AudioRendererImpl::ConvertToDuration(int bytes) { + if (bytes_per_second_) { + return base::TimeDelta::FromMicroseconds( + base::Time::kMicrosecondsPerSecond * bytes / bytes_per_second_); + } + return base::TimeDelta(); +} + bool AudioRendererImpl::IsMediaFormatSupported( const media::MediaFormat& media_format) { int channels; @@ -54,24 +66,24 @@ bool AudioRendererImpl::IsMediaFormatSupported( bool AudioRendererImpl::OnInitialize(const media::MediaFormat& media_format) { // Parse integer values in MediaFormat. - int channels; - int sample_rate; - int sample_bits; - if (!ParseMediaFormat(media_format, &channels, &sample_rate, &sample_bits)) { + if (!ParseMediaFormat(media_format, + &channels_, + &sample_rate_, + &sample_bits_)) { return false; } // Create the audio output stream in browser process. - size_t bytes_per_second = sample_rate * channels * sample_bits / 8; - size_t packet_size = bytes_per_second * kMillisecondsPerPacket / 1000; + 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; + 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, + AudioManager::AUDIO_PCM_LINEAR, channels_, sample_rate_, sample_bits_, packet_size, buffer_capacity)); return true; } @@ -153,13 +165,19 @@ void AudioRendererImpl::OnCreated(base::SharedMemoryHandle handle, shared_memory_size_ = length; } -void AudioRendererImpl::OnRequestPacket() { +void AudioRendererImpl::OnRequestPacket(size_t bytes_in_buffer, + const base::Time& message_timestamp) { DCHECK(MessageLoop::current() == io_loop_); { AutoLock auto_lock(lock_); DCHECK(!pending_request_); pending_request_ = true; + + // Use the information provided by the IPC message to adjust the playback + // delay. + request_timestamp_ = message_timestamp; + request_delay_ = ConvertToDuration(bytes_in_buffer); } // Try to fill in the fulfil the packet request. @@ -253,12 +271,32 @@ void AudioRendererImpl::OnNotifyPacketReady() { return; if (pending_request_ && playback_rate_ > 0.0f) { DCHECK(shared_memory_.get()); + + // Adjust the playback delay. + base::Time current_time = base::Time::Now(); + + // Save a local copy of the request delay. + base::TimeDelta request_delay = request_delay_; + if (current_time > request_timestamp_) { + base::TimeDelta receive_latency = current_time - request_timestamp_; + + // If the receive latency is too much it may offset all the delay. + if (receive_latency >= request_delay) { + request_delay = base::TimeDelta(); + } else { + request_delay -= receive_latency; + } + } + size_t filled = FillBuffer(static_cast<uint8*>(shared_memory_->memory()), shared_memory_size_, - playback_rate_); + playback_rate_, + request_delay); // TODO(hclam): we should try to fill in the buffer as much as possible. if (filled > 0) { pending_request_ = false; + request_delay_ = base::TimeDelta(); + request_timestamp_ = base::Time(); // Then tell browser process we are done filling into the buffer. filter_->Send( new ViewHostMsg_NotifyAudioPacketReady(0, stream_id_, filled)); diff --git a/chrome/renderer/media/audio_renderer_impl.h b/chrome/renderer/media/audio_renderer_impl.h index 6a24dfc..162cf09 100644 --- a/chrome/renderer/media/audio_renderer_impl.h +++ b/chrome/renderer/media/audio_renderer_impl.h @@ -116,7 +116,8 @@ class AudioRendererImpl : public media::AudioRendererBase, // Methods called on IO thread ---------------------------------------------- // AudioMessageFilter::Delegate methods, called by AudioMessageFilter. - void OnRequestPacket(); + void OnRequestPacket(size_t bytes_in_buffer, + const base::Time& message_timestamp); void OnStateChanged(AudioOutputStream::State state, int info); void OnCreated(base::SharedMemoryHandle handle, size_t length); void OnVolume(double left, double right); @@ -144,6 +145,11 @@ class AudioRendererImpl : public media::AudioRendererBase, explicit AudioRendererImpl(AudioMessageFilter* filter); virtual ~AudioRendererImpl(); + // Helper methods. + // Convert number of bytes to duration of time using information about the + // number of channels, sample rate and sample bits. + base::TimeDelta ConvertToDuration(int bytes); + // Methods call on IO thread ------------------------------------------------ // The following methods are tasks posted on the IO thread that needs to // be executed on that thread. They interact with AudioMessageFilter and @@ -157,6 +163,12 @@ class AudioRendererImpl : public media::AudioRendererBase, void OnNotifyPacketReady(); void OnDestroy(); + // Information about the audio stream. + int channels_; + int sample_rate_; + int sample_bits_; + size_t bytes_per_second_; + scoped_refptr<AudioMessageFilter> filter_; // ID of the stream created in the browser process. @@ -170,16 +182,32 @@ class AudioRendererImpl : public media::AudioRendererBase, MessageLoop* io_loop_; // Protects: - // - |playback_rate_| // - |stopped_| + // - |playback_rate_| // - |pending_request_| + // - |request_timestamp_| + // - |request_delay_| Lock lock_; + + // A flag that indicates this filter is called to stop. bool stopped_; - bool pending_request_; + + // Keeps the current playback rate. float playback_rate_; + // A flag that indicates an outstanding packet request. + bool pending_request_; + + // The time when a request is made. + base::Time request_timestamp_; + + // The delay for the requested packet to be played. + base::TimeDelta request_delay_; + // State variables for prerolling. bool prerolling_; + + // Remaining bytes for prerolling to complete. size_t preroll_bytes_; DISALLOW_COPY_AND_ASSIGN(AudioRendererImpl); diff --git a/media/filters/audio_renderer_base.cc b/media/filters/audio_renderer_base.cc index 22d275b..681e84b 100644 --- a/media/filters/audio_renderer_base.cc +++ b/media/filters/audio_renderer_base.cc @@ -99,10 +99,15 @@ void AudioRendererBase::OnReadComplete(Buffer* buffer_in) { } // TODO(scherkus): clean up FillBuffer().. it's overly complex!! -size_t AudioRendererBase::FillBuffer(uint8* dest, size_t dest_len, - float rate) { +size_t AudioRendererBase::FillBuffer(uint8* dest, + size_t dest_len, + float rate, + const base::TimeDelta& playback_delay) { size_t buffers_released = 0; size_t dest_written = 0; + + // The timestamp of the last buffer written during the last call to + // FillBuffer(). base::TimeDelta last_fill_buffer_time; { AutoLock auto_lock(lock_); @@ -198,6 +203,14 @@ size_t AudioRendererBase::FillBuffer(uint8* dest, size_t dest_len, // Update the pipeline's time if it was set last time. if (last_fill_buffer_time.InMicroseconds() > 0) { + // Adjust the |last_fill_buffer_time| with the playback delay. + // TODO(hclam): If there is a playback delay, the pipeline would not be + // updated with a correct timestamp when the stream is played at the very + // end since we use decoded packets to trigger time updates. A better + // solution is to start a timer when an audio packet is decoded to allow + // finer time update events. + if (playback_delay < last_fill_buffer_time) + last_fill_buffer_time -= playback_delay; host_->SetTime(last_fill_buffer_time); } diff --git a/media/filters/audio_renderer_base.h b/media/filters/audio_renderer_base.h index 83111aa..2273c7d 100644 --- a/media/filters/audio_renderer_base.h +++ b/media/filters/audio_renderer_base.h @@ -58,7 +58,7 @@ class AudioRendererBase : public AudioRenderer { virtual void OnReadComplete(Buffer* buffer_in); // Fills the given buffer with audio data by dequeuing buffers and copying the - // data into the |dest|. FillBuffer also takes care of updating the clock. + // data into the |dest|. FillBuffer() also takes care of updating the clock. // Returns the number of bytes copied into |dest|, which may be less than // equal to |len|. // @@ -67,8 +67,18 @@ class AudioRendererBase : public AudioRenderer { // enough. In such scenarios, the callee should zero out unused portions // of their buffer to playback silence. // + // FillBuffer() updates the pipeline's playback timestamp. If FillBuffer() is + // not called at the same rate as audio samples are played, then the reported + // timestamp in the pipeline will be ahead of the actual audio playback. In + // this case |playback_delay| should be used to indicate when in the future + // should the filled buffer be played. If FillBuffer() is called as the audio + // hardware plays the buffer, then |playback_delay| should be zero. + // // Safe to call on any thread. - size_t FillBuffer(uint8* dest, size_t len, float rate); + size_t FillBuffer(uint8* dest, + size_t len, + float rate, + const base::TimeDelta& playback_delay); // Helper to parse a media format and return whether we were successful // retrieving all the information we care about. diff --git a/media/filters/audio_renderer_impl.cc b/media/filters/audio_renderer_impl.cc index 95e1401..180aa76 100644 --- a/media/filters/audio_renderer_impl.cc +++ b/media/filters/audio_renderer_impl.cc @@ -57,7 +57,8 @@ size_t AudioRendererImpl::OnMoreData(AudioOutputStream* stream, void* dest_void, // TODO(scherkus): Maybe change OnMoreData to pass in char/uint8 or similar. // TODO(fbarchard): Waveout_output_win.h should handle zero length buffers // without clicking. - return FillBuffer(static_cast<uint8*>(dest_void), len, playback_rate_); + return FillBuffer(static_cast<uint8*>(dest_void), len, + playback_rate_, base::TimeDelta()); } void AudioRendererImpl::OnClose(AudioOutputStream* stream) { diff --git a/media/filters/null_audio_renderer.cc b/media/filters/null_audio_renderer.cc index b01e939..db72837 100644 --- a/media/filters/null_audio_renderer.cc +++ b/media/filters/null_audio_renderer.cc @@ -51,7 +51,10 @@ void NullAudioRenderer::ThreadMain() { // Only consume buffers when actually playing. if (playback_rate_ > 0.0f) { - size_t bytes = FillBuffer(buffer_.get(), buffer_size_, playback_rate_); + size_t bytes = FillBuffer(buffer_.get(), + buffer_size_, + playback_rate_, + base::TimeDelta()); // Calculate our sleep duration, taking playback rate into consideration. sleep_in_milliseconds = |