diff options
author | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-16 21:30:38 +0000 |
---|---|---|
committer | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-16 21:30:38 +0000 |
commit | f387b35af395c0a636a721b5cef33a46444a0b27 (patch) | |
tree | 347318649249634fc94b1965a856034ca7a305b2 /chrome | |
parent | d74aa110e3084556ba0e99b0f5d9efa94960b3d1 (diff) | |
download | chromium_src-f387b35af395c0a636a721b5cef33a46444a0b27.zip chromium_src-f387b35af395c0a636a721b5cef33a46444a0b27.tar.gz chromium_src-f387b35af395c0a636a721b5cef33a46444a0b27.tar.bz2 |
Since the introduction of PushSource, there are two buffering layers in the
browser process, the hardware buffer used in AudioOutputStream and
transportation buffer in PushSource. Together with the latency in the IPC
audio layer we have a serious AV sync problem.
To compensate the delay and latency introduced by these three factors
two parameters are added in RequestAudioPacket message that include
the buffer fill level and timestamp of the request. These two parameters
are used to determine the playback delay to be used by the audio
renderer to update the pipeline with the time delta.
So we have three parameters we need to care about:
1. Hardware buffer in AudioOutputStream
2. Buffered data in PushSource
3. IPC latency
We have accurate values for 2 and 3 but not 1. We currently don't have the
API in AudioOutputStream to query the remaining buffer in the hardware
buffer. But usually there is a large amount of data in it, e.g. on Windows
400ms worth of data. Since we now detached the hardware buffer request of
OnMoreData() from the actual packet request of IPC (by the introduction of
PushSource), it is really critical to know the buffer level in the hardware.
I made a guess of this buffer level by using the amount of last buffer copy.
Review URL: http://codereview.chromium.org/122020
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18536 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-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 |
8 files changed, 338 insertions, 215 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); |