Chromium Code Reviews| Index: media/filters/audio_renderer_base.cc |
| diff --git a/media/filters/audio_renderer_base.cc b/media/filters/audio_renderer_base.cc |
| index a319f64d126c6b1aa9a842bb59704d43f4bad930..cba4ce5590fb25cac16ee01b479f8eabe0a67a5b 100644 |
| --- a/media/filters/audio_renderer_base.cc |
| +++ b/media/filters/audio_renderer_base.cc |
| @@ -4,22 +4,26 @@ |
| #include "media/filters/audio_renderer_base.h" |
| -#include <algorithm> |
| -#include <string> |
| +#include <math.h> |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/logging.h" |
| #include "media/base/filter_host.h" |
| +#include "media/audio/audio_util.h" |
| namespace media { |
| -AudioRendererBase::AudioRendererBase() |
| +AudioRendererBase::AudioRendererBase(media::AudioRendererSink* sink) |
| : state_(kUninitialized), |
| pending_read_(false), |
| received_end_of_stream_(false), |
| rendered_end_of_stream_(false), |
| bytes_per_frame_(0), |
| + bytes_per_second_(0), |
| + stopped_(false), |
| + sink_(sink), |
| + is_initialized_(false), |
| read_cb_(base::Bind(&AudioRendererBase::DecodedAudioReady, |
| base::Unretained(this))) { |
| } |
| @@ -31,25 +35,55 @@ AudioRendererBase::~AudioRendererBase() { |
| } |
| void AudioRendererBase::Play(const base::Closure& callback) { |
| - base::AutoLock auto_lock(lock_); |
| - DCHECK_EQ(kPaused, state_); |
| - state_ = kPlaying; |
| - callback.Run(); |
| -} |
| + { |
| + base::AutoLock auto_lock(lock_); |
| + DCHECK_EQ(kPaused, state_); |
| + state_ = kPlaying; |
| + callback.Run(); |
| + } |
| -void AudioRendererBase::Pause(const base::Closure& callback) { |
| - base::AutoLock auto_lock(lock_); |
| - DCHECK(state_ == kPlaying || state_ == kUnderflow || state_ == kRebuffering); |
| - pause_cb_ = callback; |
| - state_ = kPaused; |
| + if (stopped_) |
| + return; |
| - // Pause only when we've completed our pending read. |
| - if (!pending_read_) { |
| - pause_cb_.Run(); |
| - pause_cb_.Reset(); |
| + if (GetPlaybackRate() != 0.0f) { |
| + DoPlay(); |
| } else { |
| + DoPause(); |
| + } |
| +} |
| + |
| +void AudioRendererBase::DoPlay() { |
| + earliest_end_time_ = base::Time::Now(); |
| + DCHECK(sink_.get()); |
| + sink_->Play(); |
| +} |
| + |
| +void AudioRendererBase::Pause(const base::Closure& callback) { |
| + { |
| + base::AutoLock auto_lock(lock_); |
| + DCHECK(state_ == kPlaying || state_ == kUnderflow || |
| + state_ == kRebuffering); |
| + pause_cb_ = callback; |
| state_ = kPaused; |
| + |
| + // Pause only when we've completed our pending read. |
| + if (!pending_read_) { |
| + pause_cb_.Run(); |
| + pause_cb_.Reset(); |
| + } else { |
| + state_ = kPaused; |
| + } |
| } |
| + |
| + if (stopped_) |
| + return; |
| + |
| + DoPause(); |
| +} |
| + |
| +void AudioRendererBase::DoPause() { |
| + DCHECK(sink_.get()); |
| + sink_->Pause(false); |
| } |
| void AudioRendererBase::Flush(const base::Closure& callback) { |
| @@ -57,7 +91,12 @@ void AudioRendererBase::Flush(const base::Closure& callback) { |
| } |
| void AudioRendererBase::Stop(const base::Closure& callback) { |
| - OnStop(); |
| + if (!stopped_) { |
| + DCHECK(sink_.get()); |
| + sink_->Stop(); |
| + |
| + stopped_ = true; |
| + } |
| { |
| base::AutoLock auto_lock(lock_); |
| state_ = kStopped; |
| @@ -81,12 +120,25 @@ void AudioRendererBase::Seek(base::TimeDelta time, const PipelineStatusCB& cb) { |
| seek_timestamp_ = time; |
| // Throw away everything and schedule our reads. |
| - last_fill_buffer_time_ = base::TimeDelta(); |
| + audio_time_buffered_ = base::TimeDelta(); |
| received_end_of_stream_ = false; |
| rendered_end_of_stream_ = false; |
| // |algorithm_| will request more reads. |
| algorithm_->FlushBuffers(); |
| + |
| + if (stopped_) |
| + return; |
| + |
| + DoSeek(); |
| +} |
| + |
| +void AudioRendererBase::DoSeek() { |
| + earliest_end_time_ = base::Time::Now(); |
| + |
| + // Pause and flush the stream when we seek to a new location. |
| + DCHECK(sink_.get()); |
|
scherkus (not reviewing)
2012/03/23 15:24:52
nit: DCHECK-before-dereferencing aren't very usefu
vrk (LEFT CHROMIUM)
2012/04/02 21:17:54
Deleted! Yeah I just copy/pasted logic verbatim fr
|
| + sink_->Pause(true); |
| } |
| void AudioRendererBase::Initialize(const scoped_refptr<AudioDecoder>& decoder, |
| @@ -119,16 +171,32 @@ void AudioRendererBase::Initialize(const scoped_refptr<AudioDecoder>& decoder, |
| bool config_ok = algorithm_->ValidateConfig(channels, sample_rate, |
| bits_per_channel); |
| - if (config_ok) |
| - algorithm_->Initialize(channels, sample_rate, bits_per_channel, 0.0f, cb); |
| - |
| - // Give the subclass an opportunity to initialize itself. |
| - if (!config_ok || !OnInitialize(bits_per_channel, channel_layout, |
| - sample_rate)) { |
| + if (!config_ok || is_initialized_) { |
|
scherkus (not reviewing)
2012/03/23 15:24:52
wouldn't calling Initialize() when is_initialized_
vrk (LEFT CHROMIUM)
2012/04/02 21:17:54
I agree, and actually I think the DCHECK_EQ(kUnini
|
| init_cb.Run(PIPELINE_ERROR_INITIALIZATION_FAILED); |
| return; |
| } |
| + if (config_ok) |
| + algorithm_->Initialize(channels, sample_rate, bits_per_channel, 0.0f, cb); |
| + |
| + // We use the AUDIO_PCM_LINEAR flag because AUDIO_PCM_LOW_LATENCY |
| + // does not currently support all the sample-rates that we require. |
| + // Please see: http://code.google.com/p/chromium/issues/detail?id=103627 |
| + // for more details. |
| + audio_parameters_ = AudioParameters( |
| + AudioParameters::AUDIO_PCM_LINEAR, channel_layout, sample_rate, |
| + bits_per_channel, GetHighLatencyOutputBufferSize(sample_rate)); |
| + |
| + bytes_per_second_ = audio_parameters_.GetBytesPerSecond(); |
| + |
| + DCHECK(sink_.get()); |
| + DCHECK(!is_initialized_); |
| + |
| + sink_->Initialize(audio_parameters_, this); |
| + |
| + sink_->Start(); |
| + is_initialized_ = true; |
| + |
| // Finally, execute the start callback. |
| state_ = kPaused; |
| init_cb.Run(PIPELINE_OK); |
| @@ -151,6 +219,13 @@ void AudioRendererBase::ResumeAfterUnderflow(bool buffer_more_audio) { |
| } |
| } |
| +void AudioRendererBase::SetVolume(float volume) { |
| + if (stopped_) |
| + return; |
| + DCHECK(sink_.get()); |
|
scherkus (not reviewing)
2012/03/23 15:24:52
ditto
vrk (LEFT CHROMIUM)
2012/04/02 21:17:54
Done.
|
| + sink_->SetVolume(volume); |
| +} |
| + |
| void AudioRendererBase::DecodedAudioReady(scoped_refptr<Buffer> buffer) { |
| base::AutoLock auto_lock(lock_); |
| DCHECK(state_ == kPaused || state_ == kSeeking || state_ == kPlaying || |
| @@ -205,9 +280,12 @@ void AudioRendererBase::DecodedAudioReady(scoped_refptr<Buffer> buffer) { |
| uint32 AudioRendererBase::FillBuffer(uint8* dest, |
| uint32 requested_frames, |
| const base::TimeDelta& playback_delay) { |
| - // The timestamp of the last buffer written during the last call to |
| - // FillBuffer(). |
| - base::TimeDelta last_fill_buffer_time; |
| + // The |audio_time_buffered_| is the ending timestamp of the last frame |
| + // buffered at the audio device. |playback_delay| is the amount of time |
| + // buffered at the audio device. The current time can be computed by their |
| + // difference. |
| + base::TimeDelta current_time = audio_time_buffered_ - playback_delay; |
| + |
| size_t frames_written = 0; |
| base::Closure underflow_cb; |
| { |
| @@ -231,10 +309,6 @@ uint32 AudioRendererBase::FillBuffer(uint8* dest, |
| return zeros_to_write / bytes_per_frame_; |
| } |
| - // Save a local copy of last fill buffer time and reset the member. |
| - last_fill_buffer_time = last_fill_buffer_time_; |
| - last_fill_buffer_time_ = base::TimeDelta(); |
| - |
| // Use three conditions to determine the end of playback: |
| // 1. Algorithm needs more audio data. |
| // 2. We've received an end of stream buffer. |
| @@ -250,7 +324,9 @@ uint32 AudioRendererBase::FillBuffer(uint8* dest, |
| // 3. Have not received an end of stream buffer. |
| if (algorithm_->NeedsMoreData()) { |
| if (received_end_of_stream_) { |
| - OnRenderEndOfStream(); |
| + // TODO(enal): schedule callback instead of polling. |
| + if (base::Time::Now() >= earliest_end_time_) |
| + SignalEndOfStream(); |
| } else if (state_ == kPlaying) { |
| state_ = kUnderflow; |
| underflow_cb = underflow_cb_; |
| @@ -259,17 +335,17 @@ uint32 AudioRendererBase::FillBuffer(uint8* dest, |
| // Otherwise fill the buffer. |
| frames_written = algorithm_->FillBuffer(dest, requested_frames); |
| } |
| - |
| - // Get the current time. |
| - last_fill_buffer_time_ = algorithm_->GetTime(); |
| } |
| - // Update the pipeline's time if it was set last time. |
| - base::TimeDelta new_current_time = last_fill_buffer_time - playback_delay; |
| - if (last_fill_buffer_time.InMicroseconds() > 0 && |
| - (last_fill_buffer_time != last_fill_buffer_time_ || |
| - new_current_time > host()->GetTime())) { |
| - time_cb_.Run(new_current_time, last_fill_buffer_time); |
|
vrk (LEFT CHROMIUM)
2012/03/22 21:28:53
acolwell: The bug for crbug.com/119549 is here: th
|
| + base::TimeDelta previous_time_buffered = audio_time_buffered_; |
| + // The call to FillBuffer() on |algorithm_| has increased the amount of |
| + // buffered audio data. Update the new amount of time buffered. |
| + audio_time_buffered_ = algorithm_->GetTime(); |
| + |
| + if (previous_time_buffered.InMicroseconds() > 0 && |
| + (previous_time_buffered != audio_time_buffered_ || |
| + current_time > host()->GetTime())) { |
| + time_cb_.Run(current_time, audio_time_buffered_); |
| } |
| if (!underflow_cb.is_null()) |
| @@ -295,6 +371,23 @@ void AudioRendererBase::ScheduleRead_Locked() { |
| } |
| void AudioRendererBase::SetPlaybackRate(float playback_rate) { |
| + DCHECK_LE(0.0f, playback_rate); |
| + |
| + if (!stopped_) { |
| + // Notify sink of new playback rate. |
| + sink_->SetPlaybackRate(playback_rate); |
| + |
| + // We have two cases here: |
| + // Play: GetPlaybackRate() == 0.0 && playback_rate != 0.0 |
| + // Pause: GetPlaybackRate() != 0.0 && playback_rate == 0.0 |
| + if (GetPlaybackRate() == 0.0f && playback_rate != 0.0f) { |
| + DoPlay(); |
| + } else if (GetPlaybackRate() != 0.0f && playback_rate == 0.0f) { |
| + // Pause is easy, we can always pause. |
| + DoPause(); |
| + } |
| + } |
| + |
| base::AutoLock auto_lock(lock_); |
| algorithm_->SetPlaybackRate(playback_rate); |
| } |
| @@ -309,4 +402,85 @@ bool AudioRendererBase::IsBeforeSeekTime(const scoped_refptr<Buffer>& buffer) { |
| (buffer->GetTimestamp() + buffer->GetDuration()) < seek_timestamp_; |
| } |
| +int AudioRendererBase::Render(const std::vector<float*>& audio_data, |
| + int number_of_frames, |
| + int audio_delay_milliseconds) { |
| + if (stopped_ || GetPlaybackRate() == 0.0f) { |
| + // Output silence if stopped. |
| + for (size_t i = 0; i < audio_data.size(); ++i) |
| + memset(audio_data[i], 0, sizeof(float) * number_of_frames); |
| + return 0; |
| + } |
| + |
| + // Adjust the playback delay. |
| + base::TimeDelta request_delay = |
| + base::TimeDelta::FromMilliseconds(audio_delay_milliseconds); |
| + |
| + // Finally we need to adjust the delay according to playback rate. |
| + if (GetPlaybackRate() != 1.0f) { |
| + request_delay = base::TimeDelta::FromMicroseconds( |
| + static_cast<int64>(ceil(request_delay.InMicroseconds() * |
| + GetPlaybackRate()))); |
| + } |
| + |
| + int bytes_per_frame = audio_parameters_.GetBytesPerFrame(); |
| + |
| + const int buf_size = number_of_frames * bytes_per_frame; |
| + scoped_array<uint8> buf(new uint8[buf_size]); |
| + |
| + int frames_filled = FillBuffer(buf.get(), number_of_frames, request_delay); |
| + int bytes_filled = frames_filled * bytes_per_frame; |
| + DCHECK_LE(bytes_filled, buf_size); |
| + UpdateEarliestEndTime(bytes_filled, request_delay, base::Time::Now()); |
| + |
| + // Deinterleave each audio channel. |
| + int channels = audio_data.size(); |
| + for (int channel_index = 0; channel_index < channels; ++channel_index) { |
| + media::DeinterleaveAudioChannel(buf.get(), |
| + audio_data[channel_index], |
| + channels, |
| + channel_index, |
| + bytes_per_frame / channels, |
| + frames_filled); |
| + |
| + // If FillBuffer() didn't give us enough data then zero out the remainder. |
| + if (frames_filled < number_of_frames) { |
| + int frames_to_zero = number_of_frames - frames_filled; |
| + memset(audio_data[channel_index] + frames_filled, |
| + 0, |
| + sizeof(float) * frames_to_zero); |
| + } |
| + } |
| + return frames_filled; |
| +} |
| + |
| +void AudioRendererBase::UpdateEarliestEndTime(int bytes_filled, |
| + base::TimeDelta request_delay, |
| + base::Time time_now) { |
| + if (bytes_filled != 0) { |
| + base::TimeDelta predicted_play_time = ConvertToDuration(bytes_filled); |
| + float playback_rate = GetPlaybackRate(); |
| + if (playback_rate != 1.0f) { |
| + predicted_play_time = base::TimeDelta::FromMicroseconds( |
| + static_cast<int64>(ceil(predicted_play_time.InMicroseconds() * |
| + playback_rate))); |
| + } |
| + earliest_end_time_ = |
| + std::max(earliest_end_time_, |
| + time_now + request_delay + predicted_play_time); |
| + } |
| +} |
| + |
| +base::TimeDelta AudioRendererBase::ConvertToDuration(int bytes) { |
| + if (bytes_per_second_) { |
| + return base::TimeDelta::FromMicroseconds( |
| + base::Time::kMicrosecondsPerSecond * bytes / bytes_per_second_); |
| + } |
| + return base::TimeDelta(); |
| +} |
| + |
| +void AudioRendererBase::OnRenderError() { |
| + host()->DisableAudioRenderer(); |
| +} |
| + |
| } // namespace media |