| // Copyright 2016 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 "media/base/silent_sink_suspender.h" |
| |
| #include "base/bind.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| |
| namespace media { |
| |
| SilentSinkSuspender::SilentSinkSuspender( |
| AudioRendererSink::RenderCallback* callback, |
| base::TimeDelta silence_timeout, |
| const AudioParameters& params, |
| scoped_refptr<AudioRendererSink> sink, |
| scoped_refptr<base::SingleThreadTaskRunner> worker) |
| : callback_(callback), |
| params_(params), |
| sink_(std::move(sink)), |
| task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| silence_timeout_(silence_timeout), |
| fake_sink_(std::move(worker), params_), |
| sink_transition_callback_( |
| base::BindRepeating(&SilentSinkSuspender::TransitionSinks, |
| base::Unretained(this))) { |
| DCHECK(params_.IsValid()); |
| DCHECK(sink_); |
| DCHECK(callback_); |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| } |
| |
| SilentSinkSuspender::~SilentSinkSuspender() { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| fake_sink_.Stop(); |
| } |
| |
| int SilentSinkSuspender::Render(base::TimeDelta delay, |
| base::TimeTicks delay_timestamp, |
| int prior_frames_skipped, |
| AudioBus* dest) { |
| // Lock required since AudioRendererSink::Pause() is not synchronous, we need |
| // to discard these calls during the transition to the fake sink. |
| base::AutoLock al(transition_lock_); |
| if (is_using_fake_sink_ && dest) { |
| // Audio should be silent at this point, if not, it will be handled once the |
| // transition to the fake sink is complete. |
| dest->Zero(); |
| return dest->frames(); |
| } |
| |
| // When we're using the |fake_sink_| a null destination will be sent; we store |
| // the audio data for a future transition out of silence. |
| if (!dest) { |
| DCHECK(is_using_fake_sink_); |
| DCHECK_EQ(prior_frames_skipped, 0); |
| // |delay_timestamp| contains the value cached at |
| // |latest_output_delay_timestamp_| |
| // so we simulate the real sink output, promoting |delay_timestamp| with |
| // |elapsed_time|. |
| DCHECK_EQ(delay_timestamp, latest_output_delay_timestamp_); |
| base::TimeDelta elapsed_time = |
| base::TimeTicks::Now() - fake_sink_transition_time_; |
| delay_timestamp += elapsed_time; |
| |
| // If we have no buffers or a transition is pending, one or more extra |
| // Render() calls have occurred in before TransitionSinks() can run, so we |
| // store this data for the eventual transition. |
| if (buffers_after_silence_.empty() || is_transition_pending_) |
| buffers_after_silence_.push_back(AudioBus::Create(params_)); |
| dest = buffers_after_silence_.back().get(); |
| } else if (!buffers_after_silence_.empty()) { |
| // Drain any non-silent transitional buffers before queuing more audio data. |
| // Note: These do not skew clocks derived from frame count since we don't |
| // issue Render() to the client when returning these buffers. |
| DCHECK(!is_using_fake_sink_); |
| buffers_after_silence_.front()->CopyTo(dest); |
| buffers_after_silence_.pop_front(); |
| return dest->frames(); |
| } |
| |
| // Pass-through to client and request rendering. |
| callback_->Render(delay, delay_timestamp, prior_frames_skipped, dest); |
| |
| // Check for silence or real audio data and transition if necessary. |
| if (!dest->AreFramesZero() || !detect_silence_) { |
| first_silence_time_ = base::TimeTicks(); |
| if (is_using_fake_sink_) { |
| is_transition_pending_ = true; |
| task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(sink_transition_callback_.callback(), false)); |
| return dest->frames(); |
| } |
| } else if (!is_using_fake_sink_) { |
| const base::TimeTicks now = base::TimeTicks::Now(); |
| if (first_silence_time_.is_null()) |
| first_silence_time_ = now; |
| if (now - first_silence_time_ > silence_timeout_) { |
| is_transition_pending_ = true; |
| latest_output_delay_ = delay; |
| latest_output_delay_timestamp_ = delay_timestamp; |
| fake_sink_transition_time_ = now; |
| task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(sink_transition_callback_.callback(), true)); |
| } |
| } |
| |
| return dest->frames(); |
| } |
| |
| void SilentSinkSuspender::OnRenderError() { |
| callback_->OnRenderError(); |
| } |
| |
| void SilentSinkSuspender::OnPaused() { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| |
| // This is a no-op if the sink isn't running, but must be executed without the |
| // |transition_lock_| being held to avoid possible deadlock. |
| fake_sink_.Stop(); |
| |
| base::AutoLock al(transition_lock_); |
| |
| // Nothing to do if we haven't touched the sink. |
| if (!is_using_fake_sink_ && !is_transition_pending_) { |
| first_silence_time_ = base::TimeTicks(); |
| return; |
| } |
| |
| // If we've moved over to the fake sink, we just need to stop it and cancel |
| // any pending transitions. |
| is_using_fake_sink_ = false; |
| is_transition_pending_ = false; |
| first_silence_time_ = base::TimeTicks(); |
| sink_transition_callback_.Reset(base::BindRepeating( |
| &SilentSinkSuspender::TransitionSinks, base::Unretained(this))); |
| } |
| |
| void SilentSinkSuspender::SetDetectSilence(bool detect_silence) { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| |
| bool should_transition_to_real_sink = false; |
| { |
| base::AutoLock lock(transition_lock_); |
| detect_silence_ = detect_silence; |
| should_transition_to_real_sink = !detect_silence_ && is_using_fake_sink_; |
| |
| // If we haven't started the transition abort it. |
| if (is_transition_pending_) { |
| is_transition_pending_ = false; |
| sink_transition_callback_.Reset(base::BindRepeating( |
| &SilentSinkSuspender::TransitionSinks, base::Unretained(this))); |
| } |
| } |
| |
| if (should_transition_to_real_sink) |
| TransitionSinks(/*use_fake_sink=*/false); |
| } |
| |
| bool SilentSinkSuspender::IsUsingFakeSinkForTesting() { |
| base::AutoLock al(transition_lock_); |
| return is_using_fake_sink_; |
| } |
| |
| void SilentSinkSuspender::TransitionSinks(bool use_fake_sink) { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| |
| // Ignore duplicate requests which can occur if the transition takes too long |
| // and multiple Render() events occur. |
| { |
| base::AutoLock al(transition_lock_); |
| if (use_fake_sink == is_using_fake_sink_) |
| return; |
| } |
| |
| if (use_fake_sink) { |
| sink_->Pause(); |
| |
| // |sink_| may still be executing Render() at this point or even sometime |
| // after this point, so we must acquire the lock to make sure we don't have |
| // concurrent Render() execution. Once |is_using_fake_sink_| is set to true, |
| // calls from |sink_| will be dropped. |
| { |
| base::AutoLock al(transition_lock_); |
| is_transition_pending_ = false; |
| is_using_fake_sink_ = true; |
| } |
| fake_sink_.Start(base::BindRepeating( |
| [](SilentSinkSuspender* suspender, base::TimeDelta frozen_delay, |
| base::TimeTicks frozen_delay_timestamp, base::TimeTicks ideal_time, |
| base::TimeTicks now) { |
| // TODO: Seems that the code in Render() might benefit from the two |
| // new timestamps being provided by FakeAudioWorker, in that it's call |
| // to base::TimeTicks::Now() can be eliminated (use |now| instead), |
| // along with its custom delay timestamp calculations. |
| suspender->Render(frozen_delay, frozen_delay_timestamp, 0, nullptr); |
| }, |
| this, latest_output_delay_, latest_output_delay_timestamp_)); |
| } else { |
| fake_sink_.Stop(); |
| |
| // Despite the fake sink having a synchronous Stop(), if this transition |
| // occurs too soon after pausing the real sink, we may have pending Render() |
| // calls from before the transition to the fake sink. As such, we need to |
| // hold the lock here to avoid any races. |
| { |
| base::AutoLock al(transition_lock_); |
| is_transition_pending_ = false; |
| is_using_fake_sink_ = false; |
| } |
| sink_->Play(); |
| } |
| } |
| |
| } // namespace media |