blob: 2b67a736a1f75a6ace879f5efbdc2b5ab8af756c [file] [log] [blame]
// 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.
#ifndef SRC_MEDIA_AUDIO_AUDIO_CORE_THROTTLE_OUTPUT_H_
#define SRC_MEDIA_AUDIO_AUDIO_CORE_THROTTLE_OUTPUT_H_
#include <fuchsia/media/cpp/fidl.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <fbl/ref_ptr.h>
#include "src/lib/fxl/synchronization/thread_annotations.h"
#include "src/media/audio/audio_core/audio_output.h"
#include "src/media/audio/audio_core/pipeline_config.h"
#include "src/media/audio/lib/clock/audio_clock.h"
#include "src/media/audio/lib/clock/clone_mono.h"
#include "src/media/audio/lib/clock/utils.h"
namespace media::audio {
class AudioDriver;
static constexpr zx::duration TRIM_PERIOD = zx::msec(10);
// Throttle output may only be owned on the FIDL thread.
class ThrottleOutput : public AudioOutput {
public:
static std::shared_ptr<AudioOutput> Create(const DeviceConfig& config,
ThreadingModel* threading_model,
DeviceRegistry* registry, LinkMatrix* link_matrix,
std::shared_ptr<AudioClockFactory> clock_factory) {
return std::make_shared<ThrottleOutput>(config, threading_model, registry, link_matrix,
clock_factory);
}
// Establish an audio clock (clone of monotonic) and override the default reference_clock()
// implementation that calls into the AudioDriver, because we don't have an associated driver.
ThrottleOutput(const DeviceConfig& config, ThreadingModel* threading_model,
DeviceRegistry* registry, LinkMatrix* link_matrix,
std::shared_ptr<AudioClockFactory> clock_factory)
: AudioOutput("throttle", config, threading_model, registry, link_matrix, clock_factory,
nullptr /* EffectsLoaderV2 */, std::make_unique<AudioDriver>(this)),
audio_clock_(clock_factory->CreateDeviceFixed(audio::clock::CloneOfMonotonic(),
AudioClock::kMonotonicDomain)) {
const auto ref_now = reference_clock().Read();
const auto fps = PipelineConfig::kDefaultMixGroupRate;
ref_time_to_frac_presentation_frame_ =
TimelineFunction(0, ref_now.get(), Fixed(fps).raw_value(), zx::sec(1).get());
ref_time_to_frac_safe_read_or_write_frame_ =
TimelineFunction(0, ref_now.get(), Fixed(fps).raw_value(), zx::sec(1).get());
// This is just some placeholder format that we can use to instantiate a mix
// stage for us. Since we never return a value from |StartMixJob|, we'll only
// ever trim on this mix stage, so the format here is not particularly
// important.
//
// Longer term we should just have something like a 'NullMixStage' that only
// has this trim capability.
// This must be non-0, but it doesn't actually matter much since we'll never mix with a throttle
// output.
const uint32_t kMaxBatchSize = zx_system_get_page_size();
SetupMixTask(DeviceConfig::OutputDeviceProfile(), kMaxBatchSize,
driver_ref_time_to_frac_presentation_frame());
}
~ThrottleOutput() override = default;
AudioClock& reference_clock() override { return *audio_clock_; }
protected:
// AudioOutput Implementation
void OnWakeup() FXL_EXCLUSIVE_LOCKS_REQUIRED(mix_domain().token()) override {
if (uninitialized_) {
last_sched_time_mono_ = async::Now(mix_domain().dispatcher());
UpdatePlugState(true, zx::time(0));
Process();
uninitialized_ = false;
}
}
std::optional<AudioOutput::FrameSpan> StartMixJob(zx::time ref_time) override {
// Compute the next callback time; check whether trimming is falling behind.
last_sched_time_mono_ = last_sched_time_mono_ + TRIM_PERIOD;
auto mono_time = reference_clock().MonotonicTimeFromReferenceTime(ref_time);
if (mono_time > last_sched_time_mono_) {
// TODO(mpuryear): Trimming is falling behind. We should tell someone.
last_sched_time_mono_ = mono_time + TRIM_PERIOD;
}
// TODO(mpuryear): Optimize Trim by scheduling at the end of our first pending packet, instead
// of polling. This will also make our timing in returning packets more consistent.
//
// To do this, we would need wake and recompute, whenever an AudioRenderer client changes its
// rate transformation. For now, just polling is simpler.
SetNextSchedTimeMono(last_sched_time_mono_);
// Throttle outputs don't actually mix; they provide backpressure to the
// pipeline by holding AudioPacket references until they are presented. We
// only need to schedule our next callback to keep things running, and let
// the base class implementation handle trimming the output.
return std::nullopt;
}
void WriteMixOutput(int64_t start, int64_t length, const float* payload) override {
// Since we never start any jobs, this should never be called.
FX_CHECK(false);
}
void FinishMixJob(const AudioOutput::FrameSpan& span) override {
// Since we never start any jobs, this should never be called.
FX_CHECK(false);
}
zx::duration MixDeadline() const override {
// Since we never actually mix (StartMixJob always returns nullopt), this number doesn't matter.
// Return infinity to avoid spurious "underflow" messages.
return zx::duration::infinite();
}
// AudioDevice implementation.
// No one should ever be trying to apply gain limits for a throttle output.
void ApplyGainLimits(fuchsia::media::AudioGainInfo* in_out_info,
fuchsia::media::AudioGainValidFlags set_flags) override {
FX_DCHECK(false);
}
virtual zx::time last_sched_time_mono() { return last_sched_time_mono_; }
// Override these since we don't have a real driver.
const TimelineFunction& driver_ref_time_to_frac_presentation_frame() const override {
return ref_time_to_frac_presentation_frame_;
}
const TimelineFunction& driver_ref_time_to_frac_safe_read_or_write_frame() const override {
return ref_time_to_frac_safe_read_or_write_frame_;
}
private:
zx::time last_sched_time_mono_;
bool uninitialized_ = true;
TimelineFunction ref_time_to_frac_presentation_frame_;
TimelineFunction ref_time_to_frac_safe_read_or_write_frame_;
std::unique_ptr<AudioClock> audio_clock_;
};
} // namespace media::audio
#endif // SRC_MEDIA_AUDIO_AUDIO_CORE_THROTTLE_OUTPUT_H_