| // Copyright (c) 2012 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/audio_renderer_mixer.h" |
| |
| #include <cmath> |
| |
| #include "base/check_op.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "media/base/audio_renderer_mixer_input.h" |
| #include "media/base/audio_timestamp_helper.h" |
| |
| namespace media { |
| |
| constexpr base::TimeDelta kPauseDelay = base::Seconds(10); |
| |
| AudioRendererMixer::AudioRendererMixer(const AudioParameters& output_params, |
| scoped_refptr<AudioRendererSink> sink) |
| : output_params_(output_params), |
| audio_sink_(std::move(sink)), |
| aggregate_converter_(output_params, output_params, true), |
| pause_delay_(kPauseDelay), |
| last_play_time_(base::TimeTicks::Now()), |
| // Initialize |playing_| to true since Start() results in an auto-play. |
| playing_(true) { |
| DCHECK(audio_sink_); |
| |
| // If enabled we will disable the real audio output stream for muted/silent |
| // playbacks after some time elapses. |
| RenderCallback* callback = this; |
| audio_sink_->Initialize(output_params, callback); |
| audio_sink_->Start(); |
| } |
| |
| AudioRendererMixer::~AudioRendererMixer() { |
| // AudioRendererSink must be stopped before mixer is destructed. |
| audio_sink_->Stop(); |
| |
| // Ensure that all mixer inputs have removed themselves prior to destruction. |
| DCHECK(aggregate_converter_.empty()); |
| DCHECK(converters_.empty()); |
| DCHECK(error_callbacks_.empty()); |
| } |
| |
| void AudioRendererMixer::AddMixerInput(const AudioParameters& input_params, |
| AudioConverter::InputCallback* input) { |
| base::AutoLock auto_lock(lock_); |
| if (!playing_) { |
| playing_ = true; |
| last_play_time_ = base::TimeTicks::Now(); |
| audio_sink_->Play(); |
| } |
| |
| int input_sample_rate = input_params.sample_rate(); |
| if (can_passthrough(input_sample_rate)) { |
| aggregate_converter_.AddInput(input); |
| } else { |
| auto converter = converters_.find(input_sample_rate); |
| if (converter == converters_.end()) { |
| std::pair<AudioConvertersMap::iterator, bool> result = |
| converters_.insert(std::make_pair( |
| input_sample_rate, std::make_unique<LoopbackAudioConverter>( |
| // We expect all InputCallbacks to be |
| // capable of handling arbitrary buffer |
| // size requests, disabling FIFO. |
| input_params, output_params_, true))); |
| converter = result.first; |
| |
| // Add newly-created resampler as an input to the aggregate mixer. |
| aggregate_converter_.AddInput(converter->second.get()); |
| } |
| converter->second->AddInput(input); |
| } |
| } |
| |
| void AudioRendererMixer::RemoveMixerInput( |
| const AudioParameters& input_params, |
| AudioConverter::InputCallback* input) { |
| base::AutoLock auto_lock(lock_); |
| |
| int input_sample_rate = input_params.sample_rate(); |
| if (can_passthrough(input_sample_rate)) { |
| aggregate_converter_.RemoveInput(input); |
| } else { |
| auto converter = converters_.find(input_sample_rate); |
| DCHECK(converter != converters_.end()); |
| converter->second->RemoveInput(input); |
| if (converter->second->empty()) { |
| // Remove converter when it's empty. |
| aggregate_converter_.RemoveInput(converter->second.get()); |
| converters_.erase(converter); |
| } |
| } |
| } |
| |
| void AudioRendererMixer::AddErrorCallback(AudioRendererMixerInput* input) { |
| base::AutoLock auto_lock(lock_); |
| error_callbacks_.insert(input); |
| } |
| |
| void AudioRendererMixer::RemoveErrorCallback(AudioRendererMixerInput* input) { |
| base::AutoLock auto_lock(lock_); |
| error_callbacks_.erase(input); |
| } |
| |
| bool AudioRendererMixer::CurrentThreadIsRenderingThread() { |
| return audio_sink_->CurrentThreadIsRenderingThread(); |
| } |
| |
| void AudioRendererMixer::SetPauseDelayForTesting(base::TimeDelta delay) { |
| base::AutoLock auto_lock(lock_); |
| pause_delay_ = delay; |
| } |
| |
| int AudioRendererMixer::Render(base::TimeDelta delay, |
| base::TimeTicks delay_timestamp, |
| int prior_frames_skipped, |
| AudioBus* audio_bus) { |
| TRACE_EVENT0("audio", "AudioRendererMixer::Render"); |
| base::AutoLock auto_lock(lock_); |
| |
| // If there are no mixer inputs and we haven't seen one for a while, pause the |
| // sink to avoid wasting resources when media elements are present but remain |
| // in the pause state. |
| const base::TimeTicks now = base::TimeTicks::Now(); |
| if (!aggregate_converter_.empty()) { |
| last_play_time_ = now; |
| } else if (now - last_play_time_ >= pause_delay_ && playing_) { |
| audio_sink_->Pause(); |
| playing_ = false; |
| } |
| |
| // Since AudioConverter uses uint32_t for delay calculations, we must drop |
| // negative delay values (which are incorrect anyways). |
| if (delay < base::TimeDelta()) |
| delay = base::TimeDelta(); |
| |
| uint32_t frames_delayed = |
| AudioTimestampHelper::TimeToFrames(delay, output_params_.sample_rate()); |
| aggregate_converter_.ConvertWithDelay(frames_delayed, audio_bus); |
| return audio_bus->frames(); |
| } |
| |
| void AudioRendererMixer::OnRenderError() { |
| // Call each mixer input and signal an error. |
| base::AutoLock auto_lock(lock_); |
| for (auto* input : error_callbacks_) |
| input->OnRenderError(); |
| } |
| |
| } // namespace media |