| // Copyright 2016 The Fuchsia 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 "src/media/audio/audio_core/audio_output.h" |
| |
| #include <lib/fit/defer.h> |
| #include <lib/zx/clock.h> |
| |
| #include <limits> |
| |
| #include <trace/event.h> |
| |
| #include "src/media/audio/audio_core/audio_driver.h" |
| #include "src/media/audio/audio_core/base_renderer.h" |
| #include "src/media/audio/audio_core/mixer/mixer.h" |
| #include "src/media/audio/audio_core/mixer/no_op.h" |
| #include "src/media/audio/lib/logging/logging.h" |
| |
| namespace media::audio { |
| |
| static constexpr zx::duration kMaxTrimPeriod = zx::msec(10); |
| |
| // TODO(49345): We should not need driver to be set for all Audio Devices. |
| AudioOutput::AudioOutput(ThreadingModel* threading_model, DeviceRegistry* registry, |
| LinkMatrix* link_matrix) |
| : AudioDevice(Type::Output, threading_model, registry, link_matrix, |
| std::make_unique<AudioDriverV1>(this)) { |
| next_sched_time_ = async::Now(mix_domain().dispatcher()); |
| next_sched_time_known_ = true; |
| } |
| |
| AudioOutput::AudioOutput(ThreadingModel* threading_model, DeviceRegistry* registry, |
| LinkMatrix* link_matrix, std::unique_ptr<AudioDriver> driver) |
| : AudioDevice(Type::Output, threading_model, registry, link_matrix, std::move(driver)) { |
| next_sched_time_ = async::Now(mix_domain().dispatcher()); |
| next_sched_time_known_ = true; |
| } |
| |
| void AudioOutput::Process() { |
| FX_CHECK(pipeline_); |
| auto now = async::Now(mix_domain().dispatcher()); |
| |
| int64_t trace_wake_delta = next_sched_time_known_ ? (now - next_sched_time_).get() : 0; |
| TRACE_DURATION("audio", "AudioOutput::Process", "wake delta", TA_INT64(trace_wake_delta)); |
| |
| // At this point, we should always know when our implementation would like to be called to do some |
| // mixing work next. If we do not know, then we should have already shut down. |
| // |
| // If the next sched time has not arrived yet, don't attempt to mix anything. Just trim the queues |
| // and move on. |
| FX_DCHECK(next_sched_time_known_); |
| if (now >= next_sched_time_) { |
| // Clear the flag. If the implementation does not set it during the cycle by calling |
| // SetNextSchedTime, we consider it an error and shut down. |
| next_sched_time_known_ = false; |
| |
| auto mix_frames = StartMixJob(now); |
| if (mix_frames) { |
| auto buf = pipeline_->ReadLock(now, mix_frames->start, mix_frames->length); |
| FX_CHECK(buf); |
| FinishMixJob(*mix_frames, reinterpret_cast<float*>(buf->payload())); |
| } else { |
| pipeline_->Trim(now); |
| } |
| } |
| |
| if (!next_sched_time_known_) { |
| FX_LOGS(ERROR) << "Output failed to schedule next service time. Shutting down!"; |
| ShutdownSelf(); |
| return; |
| } |
| |
| // Figure out when we should wake up to do more work again. No matter how long our implementation |
| // wants to wait, we need to make sure to wake up and periodically trim our input queues. |
| auto max_sched_time = now + kMaxTrimPeriod; |
| if (next_sched_time_ > max_sched_time) { |
| next_sched_time_ = max_sched_time; |
| } |
| zx_status_t status = mix_timer_.PostForTime(mix_domain().dispatcher(), next_sched_time_); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to schedule mix"; |
| ShutdownSelf(); |
| } |
| } |
| |
| fit::result<std::shared_ptr<Mixer>, zx_status_t> AudioOutput::InitializeSourceLink( |
| const AudioObject& source, std::shared_ptr<ReadableStream> stream) { |
| TRACE_DURATION("audio", "AudioOutput::InitializeSourceLink"); |
| |
| auto usage = source.usage(); |
| FX_DCHECK(usage) << "Source has no assigned usage"; |
| if (!usage) { |
| usage = {StreamUsage::WithRenderUsage(RenderUsage::MEDIA)}; |
| } |
| |
| if (stream) { |
| auto mixer = pipeline_->AddInput(std::move(stream), *usage); |
| const auto& settings = device_settings(); |
| if (settings != nullptr) { |
| AudioDeviceSettings::GainState cur_gain_state; |
| settings->SnapshotGainState(&cur_gain_state); |
| |
| mixer->bookkeeping().gain.SetDestGain( |
| cur_gain_state.muted |
| ? fuchsia::media::audio::MUTED_GAIN_DB |
| : std::clamp(cur_gain_state.gain_db, Gain::kMinGainDb, Gain::kMaxGainDb)); |
| } |
| return fit::ok(std::move(mixer)); |
| } |
| |
| return fit::ok(std::make_shared<audio::mixer::NoOp>()); |
| } |
| |
| void AudioOutput::CleanupSourceLink(const AudioObject& source, |
| std::shared_ptr<ReadableStream> stream) { |
| TRACE_DURATION("audio", "AudioOutput::CleanupSourceLink"); |
| if (stream) { |
| pipeline_->RemoveInput(*stream); |
| } |
| } |
| |
| fit::result<std::shared_ptr<ReadableStream>, zx_status_t> AudioOutput::InitializeDestLink( |
| const AudioObject& dest) { |
| TRACE_DURATION("audio", "AudioOutput::InitializeDestLink"); |
| if (!pipeline_) { |
| return fit::error(ZX_ERR_BAD_STATE); |
| } |
| return fit::ok(pipeline_->loopback()); |
| } |
| |
| void AudioOutput::SetupMixTask(const PipelineConfig& config, uint32_t channels, |
| size_t max_block_size_frames, |
| TimelineFunction device_reference_clock_to_fractional_frame) { |
| pipeline_ = std::make_unique<OutputPipeline>(config, channels, max_block_size_frames, |
| device_reference_clock_to_fractional_frame); |
| pipeline_->SetMinLeadTime(min_lead_time_); |
| } |
| |
| void AudioOutput::Cleanup() { |
| AudioDevice::Cleanup(); |
| mix_timer_.Cancel(); |
| } |
| |
| void AudioOutput::SetEffectConfig(const std::string& instance_name, const std::string& config) { |
| mix_domain().PostTask([this, self = shared_from_this(), instance_name, config]() { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(token, &mix_domain()); |
| if (pipeline_ && !is_shutting_down()) { |
| pipeline_->SetEffectConfig(instance_name, config); |
| } |
| }); |
| } |
| |
| } // namespace media::audio |