| // 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_input.h" |
| |
| #include <cmath> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "base/trace_event/trace_event.h" |
| #include "media/base/audio_renderer_mixer.h" |
| #include "media/base/audio_renderer_mixer_pool.h" |
| #include "media/base/audio_timestamp_helper.h" |
| |
| namespace media { |
| |
| // TODO(dalecurtis): Merge with AudioDeviceDescription::IsDefaultDevice() once |
| // that file has been moved to media/base. |
| bool IsDefaultDevice(const std::string& device_id) { |
| return device_id.empty() || device_id == "default"; |
| } |
| |
| AudioRendererMixerInput::AudioRendererMixerInput( |
| AudioRendererMixerPool* mixer_pool, |
| const base::UnguessableToken& owner_token, |
| const std::string& device_id, |
| AudioLatency::LatencyType latency) |
| : mixer_pool_(mixer_pool), |
| owner_token_(owner_token), |
| device_id_(device_id), |
| latency_(latency) { |
| DCHECK(mixer_pool_); |
| } |
| |
| AudioRendererMixerInput::~AudioRendererMixerInput() { |
| // Note: This may not happen on the thread the sink was used. E.g., this may |
| // end up destroyed on the render thread despite being used on the media |
| // thread. |
| |
| DCHECK(!started_); |
| DCHECK(!mixer_); |
| if (sink_) |
| sink_->Stop(); |
| |
| // Because GetOutputDeviceInfoAsync() and SwitchOutputDevice() both use |
| // base::RetainedRef, it should be impossible to get here with these set. |
| DCHECK(!pending_device_info_cb_); |
| DCHECK(!pending_switch_cb_); |
| } |
| |
| void AudioRendererMixerInput::Initialize( |
| const AudioParameters& params, |
| AudioRendererSink::RenderCallback* callback) { |
| DCHECK(!started_); |
| DCHECK(!mixer_); |
| DCHECK(callback); |
| |
| // Current usage ensures we always call GetOutputDeviceInfoAsync() and wait |
| // for the result before calling this method. We could add support for doing |
| // otherwise here, but it's not needed for now, so for simplicity just DCHECK. |
| DCHECK(sink_); |
| DCHECK(device_info_); |
| |
| params_ = params; |
| callback_ = callback; |
| } |
| |
| void AudioRendererMixerInput::Start() { |
| DCHECK(!started_); |
| DCHECK(!mixer_); |
| DCHECK(callback_); // Initialized. |
| |
| DCHECK(sink_); |
| DCHECK(device_info_); |
| DCHECK_EQ(device_info_->device_status(), OUTPUT_DEVICE_STATUS_OK); |
| |
| started_ = true; |
| mixer_ = mixer_pool_->GetMixer(owner_token_, params_, latency_, *device_info_, |
| std::move(sink_)); |
| |
| // Note: OnRenderError() may be called immediately after this call returns. |
| mixer_->AddErrorCallback(this); |
| } |
| |
| void AudioRendererMixerInput::Stop() { |
| // Stop() may be called at any time, if Pause() hasn't been called we need to |
| // remove our mixer input before shutdown. |
| Pause(); |
| |
| if (mixer_) { |
| mixer_->RemoveErrorCallback(this); |
| mixer_pool_->ReturnMixer(mixer_); |
| mixer_ = nullptr; |
| } |
| |
| started_ = false; |
| } |
| |
| void AudioRendererMixerInput::Play() { |
| if (playing_ || !mixer_) |
| return; |
| |
| mixer_->AddMixerInput(params_, this); |
| playing_ = true; |
| } |
| |
| void AudioRendererMixerInput::Pause() { |
| if (!playing_ || !mixer_) |
| return; |
| |
| mixer_->RemoveMixerInput(params_, this); |
| playing_ = false; |
| } |
| |
| // Flush is not supported with mixed sinks due to how delayed pausing works in |
| // the mixer. |
| void AudioRendererMixerInput::Flush() {} |
| |
| bool AudioRendererMixerInput::SetVolume(double volume) { |
| base::AutoLock auto_lock(volume_lock_); |
| volume_ = volume; |
| return true; |
| } |
| |
| OutputDeviceInfo AudioRendererMixerInput::GetOutputDeviceInfo() { |
| NOTREACHED(); // The blocking API is intentionally not supported. |
| return OutputDeviceInfo(); |
| } |
| |
| void AudioRendererMixerInput::GetOutputDeviceInfoAsync( |
| OutputDeviceInfoCB info_cb) { |
| // If we have device information for a current sink or mixer, just return it |
| // immediately. Per the AudioRendererSink API contract, this must be posted. |
| if (device_info_.has_value() && (sink_ || mixer_)) { |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(info_cb), *device_info_)); |
| return; |
| } |
| |
| if (switch_output_device_in_progress_) { |
| DCHECK(!godia_in_progress_); |
| pending_device_info_cb_ = std::move(info_cb); |
| return; |
| } |
| |
| godia_in_progress_ = true; |
| |
| // We may have |device_info_|, but a Stop() has been called since if we don't |
| // have a |sink_| or a |mixer_|, so request the information again in case it |
| // has changed (which may occur due to browser side device changes). |
| device_info_.reset(); |
| |
| // If we don't have a sink yet start the process of getting one. |
| sink_ = mixer_pool_->GetSink(owner_token_, device_id_); |
| |
| // Retain a ref to this sink to ensure it is not destructed while this occurs. |
| // The callback is guaranteed to execute on this thread, so there are no |
| // threading issues. |
| sink_->GetOutputDeviceInfoAsync( |
| base::BindOnce(&AudioRendererMixerInput::OnDeviceInfoReceived, |
| base::RetainedRef(this), std::move(info_cb))); |
| } |
| |
| bool AudioRendererMixerInput::IsOptimizedForHardwareParameters() { |
| return true; |
| } |
| |
| bool AudioRendererMixerInput::CurrentThreadIsRenderingThread() { |
| return mixer_->CurrentThreadIsRenderingThread(); |
| } |
| |
| void AudioRendererMixerInput::SwitchOutputDevice( |
| const std::string& device_id, |
| OutputDeviceStatusCB callback) { |
| // If a GODIA() call is in progress, defer until it's complete. |
| if (godia_in_progress_) { |
| DCHECK(!switch_output_device_in_progress_); |
| |
| // Abort any previous device switch which may be pending. |
| if (pending_switch_cb_) |
| std::move(pending_switch_cb_).Run(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL); |
| |
| pending_device_id_ = device_id; |
| pending_switch_cb_ = std::move(callback); |
| return; |
| } |
| |
| // Some pages send "default" instead of the spec compliant empty string for |
| // the default device. Short circuit these here to avoid busy work. |
| if (device_id == device_id_ || |
| (IsDefaultDevice(device_id_) && IsDefaultDevice(device_id))) { |
| std::move(callback).Run(OUTPUT_DEVICE_STATUS_OK); |
| return; |
| } |
| |
| switch_output_device_in_progress_ = true; |
| |
| // Request a new sink using the new device id. This process may fail, so to |
| // avoid interrupting working audio, don't set any class variables until we |
| // know it's a success. |
| auto new_sink = mixer_pool_->GetSink(owner_token_, device_id); |
| |
| // Retain a ref to this sink to ensure it is not destructed while this occurs. |
| // The callback is guaranteed to execute on this thread, so there are no |
| // threading issues. |
| new_sink->GetOutputDeviceInfoAsync( |
| base::BindOnce(&AudioRendererMixerInput::OnDeviceSwitchReady, |
| base::RetainedRef(this), std::move(callback), new_sink)); |
| } |
| |
| double AudioRendererMixerInput::ProvideInput(AudioBus* audio_bus, |
| uint32_t frames_delayed) { |
| TRACE_EVENT0("audio", "AudioRendererMixerInput::ProvideInput"); |
| const base::TimeDelta delay = |
| AudioTimestampHelper::FramesToTime(frames_delayed, params_.sample_rate()); |
| |
| int frames_filled = |
| callback_->Render(delay, base::TimeTicks::Now(), 0, audio_bus); |
| |
| // AudioConverter expects unfilled frames to be zeroed. |
| if (frames_filled < audio_bus->frames()) { |
| audio_bus->ZeroFramesPartial(frames_filled, |
| audio_bus->frames() - frames_filled); |
| } |
| |
| // We're reading |volume_| from the audio device thread and must avoid racing |
| // with the main/media thread calls to SetVolume(). See thread safety comment |
| // in the header file. |
| { |
| base::AutoLock auto_lock(volume_lock_); |
| return frames_filled > 0 ? volume_ : 0; |
| } |
| } |
| |
| void AudioRendererMixerInput::OnRenderError() { |
| callback_->OnRenderError(); |
| } |
| |
| void AudioRendererMixerInput::OnDeviceInfoReceived( |
| OutputDeviceInfoCB info_cb, |
| OutputDeviceInfo device_info) { |
| DCHECK(godia_in_progress_); |
| godia_in_progress_ = false; |
| |
| device_info_ = device_info; |
| std::move(info_cb).Run(*device_info_); |
| |
| // Complete any pending SwitchOutputDevice() if needed. We don't post this to |
| // ensure we don't reorder calls relative to what the page is expecting. I.e., |
| // if we post we could end up with Switch(1) -> Switch(2) ending on Switch(1). |
| if (!pending_switch_cb_) |
| return; |
| SwitchOutputDevice(std::move(pending_device_id_), |
| std::move(pending_switch_cb_)); |
| } |
| |
| void AudioRendererMixerInput::OnDeviceSwitchReady( |
| OutputDeviceStatusCB switch_cb, |
| scoped_refptr<AudioRendererSink> sink, |
| OutputDeviceInfo device_info) { |
| DCHECK(switch_output_device_in_progress_); |
| switch_output_device_in_progress_ = false; |
| |
| if (device_info.device_status() != OUTPUT_DEVICE_STATUS_OK) { |
| sink->Stop(); |
| std::move(switch_cb).Run(device_info.device_status()); |
| |
| // Start any pending device info request. |
| if (pending_device_info_cb_) |
| GetOutputDeviceInfoAsync(std::move(pending_device_info_cb_)); |
| |
| return; |
| } |
| |
| const bool has_mixer = !!mixer_; |
| const bool is_playing = playing_; |
| |
| // This may occur if Start() hasn't yet been called. |
| if (sink_) |
| sink_->Stop(); |
| |
| sink_ = std::move(sink); |
| device_info_ = device_info; |
| device_id_ = device_info.device_id(); |
| |
| Stop(); |
| if (has_mixer) { |
| Start(); |
| if (is_playing) |
| Play(); |
| } |
| |
| std::move(switch_cb).Run(device_info.device_status()); |
| |
| // Start any pending device info request. |
| if (pending_device_info_cb_) |
| GetOutputDeviceInfoAsync(std::move(pending_device_info_cb_)); |
| } |
| |
| } // namespace media |