Chromium Code Reviews| Index: content/renderer/media/webaudio_suspender.cc |
| diff --git a/content/renderer/media/webaudio_suspender.cc b/content/renderer/media/webaudio_suspender.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..16b98bf6783360b6b72ba3a09512505c5200efeb |
| --- /dev/null |
| +++ b/content/renderer/media/webaudio_suspender.cc |
| @@ -0,0 +1,116 @@ |
| +// 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 "content/renderer/media/webaudio_suspender.h" |
| + |
| +#include "base/single_thread_task_runner.h" |
| +#include "base/threading/thread_task_runner_handle.h" |
| + |
| +namespace content { |
| + |
| +WebAudioSuspender::WebAudioSuspender( |
| + media::AudioRendererSink::RenderCallback* callback, |
| + const media::AudioParameters& params, |
| + const scoped_refptr<media::AudioRendererSink>& sink, |
| + const scoped_refptr<base::SingleThreadTaskRunner>& worker) |
| + : callback_(callback), |
| + params_(params), |
| + sink_(sink), |
| + task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| + fake_sink_(worker, params_), |
| + sink_transition_callback_(base::Bind(&WebAudioSuspender::TransitionSinks, |
| + base::Unretained(this))) { |
| + DCHECK(params_.IsValid()); |
| + DCHECK(sink_); |
| + DCHECK(callback_); |
| + DCHECK(task_runner_->BelongsToCurrentThread()); |
| +} |
| + |
| +WebAudioSuspender::~WebAudioSuspender() { |
| + DCHECK(task_runner_->BelongsToCurrentThread()); |
| + fake_sink_.Stop(); |
| +} |
| + |
| +int WebAudioSuspender::Render(media::AudioBus* dest, |
| + uint32_t frames_delayed, |
| + uint32_t frames_skipped) { |
| + // 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(frames_delayed, 0u); |
| + DCHECK_EQ(frames_skipped, 0u); |
| + // 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(media::AudioBus::Create(params_)); |
| + dest = buffers_after_silence_.back().get(); |
| + } |
| + |
| + // Drain any non-silent transitional buffers before queuing more audio data. |
| + // Note: This may cause skew in the WebAudio clock if too many buffers occur. |
| + if (!is_using_fake_sink_ && !buffers_after_silence_.empty()) { |
| + buffers_after_silence_.front()->CopyTo(dest); |
| + buffers_after_silence_.pop_front(); |
| + return dest->frames(); |
| + } |
| + |
| + // Pass-through to client and request rendering. |
| + callback_->Render(dest, frames_delayed, frames_skipped); |
| + |
| + // Check for silence or real audio data and transition if necessary. |
| + if (!dest->AreFramesZero()) { |
| + first_silence_time_ = base::TimeTicks(); |
| + if (is_using_fake_sink_) { |
| + is_transition_pending_ = true; |
| + task_runner_->PostTask( |
| + FROM_HERE, base::Bind(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_ > silent_timeout_) { |
|
o1ka
2016/09/23 10:34:35
can be "else if"
DaleCurtis
2016/09/23 20:24:19
Not if silent timeout is 0 or negative (testing).
o1ka
2016/09/26 12:21:55
Acknowledged.
|
| + is_transition_pending_ = true; |
| + task_runner_->PostTask( |
| + FROM_HERE, base::Bind(sink_transition_callback_.callback(), true)); |
| + } |
| + } |
| + |
| + return dest->frames(); |
| +} |
| + |
| +void WebAudioSuspender::OnRenderError() { |
| + callback_->OnRenderError(); |
| +} |
| + |
| +void WebAudioSuspender::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. |
| + if (use_fake_sink && is_using_fake_sink_) |
| + return; |
| + if (!use_fake_sink && !is_using_fake_sink_) |
| + return; |
| + |
| + // It's safe to modify |is_using_fake_sink_| in between Pause() and |
| + // Play() since no sink is actively calling into Render() during that time. |
|
o1ka
2016/09/23 10:34:35
ARS interface does not guarantee that Pause() is s
DaleCurtis
2016/09/23 20:24:19
Ah thanks for pointing that out. I'd forgotten. Ad
|
| + if (use_fake_sink) { |
| + sink_->Pause(); |
| + is_transition_pending_ = false; |
| + is_using_fake_sink_ = true; |
|
o1ka
2016/09/23 10:34:34
The above means that these two flags may be concur
DaleCurtis
2016/09/23 20:24:19
Locked!
|
| + fake_sink_.Start(base::Bind(base::IgnoreResult(&WebAudioSuspender::Render), |
| + base::Unretained(this), nullptr, 0, 0)); |
| + } else { |
| + fake_sink_.Stop(); |
| + is_transition_pending_ = false; |
| + is_using_fake_sink_ = false; |
| + sink_->Play(); |
| + } |
| +} |
| + |
| +} // namespace content |