blob: 4cbf9683d07a6fc7f01d6a24a4ece11b6cbcc41a [file] [log] [blame]
// Copyright 2017 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/driver_output.h"
#include <lib/async/cpp/time.h>
#include <lib/fit/defer.h>
#include <lib/trace/event.h>
#include <lib/zx/clock.h>
#include <algorithm>
#include <iomanip>
#include "src/media/audio/audio_core/audio_driver.h"
#include "src/media/audio/audio_core/reporter.h"
constexpr bool VERBOSE_TIMING_DEBUG = false;
namespace media::audio {
static constexpr fuchsia::media::AudioSampleFormat kDefaultAudioFmt =
fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32;
static constexpr zx::duration kDefaultMaxRetentionNsec = zx::msec(60);
static constexpr zx::duration kDefaultRetentionGapNsec = zx::msec(10);
static constexpr zx::duration kUnderflowCooldown = zx::msec(1000);
static std::atomic<zx_txid_t> TXID_GEN(1);
static thread_local zx_txid_t TXID = TXID_GEN.fetch_add(1);
// Consts used if kEnableFinalMixWavWriter is set:
//
// This atomic is only used when the final-mix wave-writer is enabled --
// specifically to generate unique ids for each final-mix WAV file.
std::atomic<uint32_t> DriverOutput::final_mix_instance_num_(0u);
// WAV file location: FilePathName+final_mix_instance_num_+FileExtension
constexpr const char* kDefaultWavFilePathName = "/tmp/final_mix_";
constexpr const char* kWavFileExtension = ".wav";
DriverOutput::DriverOutput(const std::string& name, ThreadingModel* threading_model,
DeviceRegistry* registry, zx::channel initial_stream_channel,
LinkMatrix* link_matrix, VolumeCurve volume_curve)
: AudioOutput(name, threading_model, registry, link_matrix,
std::make_unique<AudioDriverV1>(this)),
initial_stream_channel_(std::move(initial_stream_channel)),
volume_curve_(volume_curve) {}
DriverOutput::DriverOutput(const std::string& name, ThreadingModel* threading_model,
DeviceRegistry* registry,
fidl::InterfaceHandle<fuchsia::hardware::audio::StreamConfig> channel,
LinkMatrix* link_matrix, VolumeCurve volume_curve)
: AudioOutput(name, threading_model, registry, link_matrix,
std::make_unique<AudioDriverV2>(this)),
initial_stream_channel_(channel.TakeChannel()),
volume_curve_(volume_curve) {}
DriverOutput::~DriverOutput() { wav_writer_.Close(); }
const PipelineConfig& DriverOutput::pipeline_config() const {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, &mix_domain());
return driver()
? config().output_device_profile(driver()->persistent_unique_id()).pipeline_config()
: config().default_output_device_profile().pipeline_config();
}
int64_t DriverOutput::RefTimeToSafeWriteFrame(zx::time ref_time) const {
auto& time_to_frac_frame = driver_ref_time_to_frac_safe_read_or_write_frame();
return Fixed::FromRaw(time_to_frac_frame.Apply(ref_time.get())).Floor();
}
zx::time DriverOutput::SafeWriteFrameToRefTime(int64_t frame) const {
auto& time_to_frac_frame = driver_ref_time_to_frac_safe_read_or_write_frame();
return zx::time(time_to_frac_frame.ApplyInverse(Fixed(frame).raw_value()));
}
TimelineRate DriverOutput::FramesPerRefTick() const {
auto frac_frame_per_tick = driver_ref_time_to_frac_safe_read_or_write_frame().rate();
return frac_frame_per_tick * TimelineRate(1, Fixed(1).raw_value());
}
zx_status_t DriverOutput::Init() {
TRACE_DURATION("audio", "DriverOutput::Init");
FX_DCHECK(state_ == State::Uninitialized);
zx_status_t res = AudioOutput::Init();
if (res != ZX_OK) {
return res;
}
res = driver()->Init(std::move(initial_stream_channel_));
if (res != ZX_OK) {
FX_PLOGS(ERROR, res) << "Failed to initialize driver object";
return res;
}
state_ = State::FormatsUnknown;
return res;
}
void DriverOutput::OnWakeup() {
TRACE_DURATION("audio", "DriverOutput::OnWakeup");
// If we are not in the FormatsUnknown state, then we have already started the
// state machine. There is (currently) nothing else to do here.
FX_DCHECK(state_ != State::Uninitialized);
if (state_ != State::FormatsUnknown) {
return;
}
// Kick off the process of driver configuration by requesting the basic driver
// info, which will include the modes which the driver supports.
driver()->GetDriverInfo();
state_ = State::FetchingFormats;
}
std::optional<AudioOutput::FrameSpan> DriverOutput::StartMixJob(zx::time ref_time) {
TRACE_DURATION("audio", "DriverOutput::StartMixJob");
if (state_ != State::Started) {
FX_LOGS(ERROR) << "Bad state during StartMixJob " << static_cast<uint32_t>(state_);
state_ = State::Shutdown;
ShutdownSelf();
return std::nullopt;
}
// TODO(mpuryear): Depending on policy, use send appropriate commands to the
// driver to control gain as well. Some policy settings which might be useful
// include...
//
// ++ Never use HW gain, even if it supports it.
// ++ Always use HW gain when present, regardless of its limitations.
// ++ Use HW gain when present, but only if it reaches a minimum bar of
// functionality.
// ++ Implement a hybrid of HW/SW gain. IOW - Get as close as possible to our
// target using HW, and then get the rest of the way there using SW
// scaling. This approach may end up being unreasonably tricky as we may
// not be able to synchronize the HW and SW changes in gain well enough to
// avoid strange situations where the jumps in one direction (because of
// the SW component), and then in the other (as the HW gain command takes
// effect).
//
bool output_muted = true;
const auto& settings = device_settings();
if (settings != nullptr) {
auto [flags, cur_gain_state] = settings->SnapshotGainState();
output_muted = cur_gain_state.muted;
}
FX_DCHECK(driver_writable_ring_buffer() != nullptr);
const auto& output_frames_per_reference_tick = FramesPerRefTick();
const auto& rb = *driver_writable_ring_buffer();
uint32_t fifo_frames = driver()->fifo_depth_frames();
// output_frames_consumed is the number of frames that the audio output
// device's DMA *may* have read so far. output_frames_transmitted is the
// slightly-smaller number of frames that have *must* have been transmitted
// over the interconnect so far. Note, this is not technically the number of
// frames which have made sound so far. Once a frame has left the
// interconnect, it still has the device's external_delay before it will
// finally hit the speaker.
int64_t output_frames_consumed = RefTimeToSafeWriteFrame(ref_time);
int64_t output_frames_transmitted = output_frames_consumed - fifo_frames;
auto mono_time = reference_clock().MonotonicTimeFromReferenceTime(ref_time);
if (output_frames_consumed >= frames_sent_) {
if (!underflow_start_time_mono_.get()) {
// If this was the first time we missed our limit, log a message, mark the start time of the
// underflow event, and fill our entire ring buffer with silence.
int64_t output_underflow_frames = output_frames_consumed - frames_sent_;
int64_t low_water_frames_underflow = output_underflow_frames + low_water_frames_;
zx::duration output_underflow_duration =
zx::nsec(output_frames_per_reference_tick.Inverse().Scale(output_underflow_frames));
FX_DCHECK(output_underflow_duration.get() >= 0);
zx::duration output_variance_from_expected_wakeup =
zx::nsec(output_frames_per_reference_tick.Inverse().Scale(low_water_frames_underflow));
TRACE_INSTANT("audio", "DriverOutput::UNDERFLOW", TRACE_SCOPE_THREAD);
TRACE_ALERT("audio", "audiounderflow");
FX_LOGS(ERROR) << "OUTPUT UNDERFLOW: Missed mix target by (worst-case, expected) = ("
<< std::setprecision(4)
<< static_cast<double>(output_underflow_duration.to_nsecs()) / ZX_MSEC(1)
<< ", " << output_variance_from_expected_wakeup.to_msecs()
<< ") ms. Cooling down for " << kUnderflowCooldown.to_msecs()
<< " milliseconds.";
reporter().DeviceUnderflow(mono_time, mono_time + output_underflow_duration);
underflow_start_time_mono_ = mono_time;
output_producer_->FillWithSilence(rb.virt(), rb.frames());
zx_cache_flush(rb.virt(), rb.size(), ZX_CACHE_FLUSH_DATA);
wav_writer_.Close();
}
// Regardless of whether this was the first or a subsequent underflow,
// update the cooldown deadline (the time at which we will start producing
// frames again, provided we don't underflow again)
underflow_cooldown_deadline_mono_ = zx::deadline_after(kUnderflowCooldown);
}
// We want to fill up to be HighWaterNsec ahead of the current safe write
// pointer position. Add HighWaterNsec to our concept of "now" and run it
// through our transformation to figure out what frame number this.
int64_t fill_target = RefTimeToSafeWriteFrame(ref_time + kDefaultHighWaterNsec);
// Are we in the middle of an underflow cooldown? If so, check whether we have recovered yet.
if (underflow_start_time_mono_.get()) {
if (mono_time < underflow_cooldown_deadline_mono_) {
// Looks like we have not recovered yet. Pretend to have produced the
// frames we were going to produce and schedule the next wakeup time.
frames_sent_ = fill_target;
ScheduleNextLowWaterWakeup();
return std::nullopt;
} else {
// Looks like we recovered. Log and go back to mixing.
FX_LOGS(WARNING) << "OUTPUT UNDERFLOW: Recovered after "
<< (mono_time - underflow_start_time_mono_).to_msecs() << " ms.";
underflow_start_time_mono_ = zx::time(0);
underflow_cooldown_deadline_mono_ = zx::time(0);
}
}
// Compute the number of frames which are currently "in flight". We define
// this as the number of frames that we have rendererd into the ring buffer
// but which have may have not been transmitted over the output's interconnect
// yet. The distance between frames_sent_ and output_frames_transmitted
// should give us this number.
int64_t frames_in_flight = frames_sent_ - output_frames_transmitted;
FX_DCHECK((frames_in_flight >= 0) && (frames_in_flight <= rb.frames()));
FX_DCHECK(frames_sent_ <= fill_target);
int64_t desired_frames = fill_target - frames_sent_;
// If we woke up too early to have any work to do, just get out now.
if (desired_frames == 0) {
return std::nullopt;
}
uint32_t rb_space = rb.frames() - static_cast<uint32_t>(frames_in_flight);
if (desired_frames > rb.frames()) {
FX_LOGS(ERROR) << "OUTPUT OVERFLOW: want to produce " << desired_frames
<< " but the ring buffer is only " << rb.frames() << " frames long.";
return std::nullopt;
}
uint32_t frames_to_mix = static_cast<uint32_t>(std::min<int64_t>(rb_space, desired_frames));
return AudioOutput::FrameSpan{
.start = frames_sent_,
.length = frames_to_mix,
.is_mute = output_muted,
};
}
void DriverOutput::WriteToRing(const AudioOutput::FrameSpan& span, const float* buffer) {
TRACE_DURATION("audio", "DriverOutput::WriteToRing");
const auto& rb = driver_writable_ring_buffer();
FX_DCHECK(rb != nullptr);
size_t frames_left = span.length;
size_t offset = 0;
while (frames_left > 0) {
uint32_t wr_ptr = (span.start + offset) % rb->frames();
uint32_t contig_space = rb->frames() - wr_ptr;
uint32_t to_send = frames_left;
if (to_send > contig_space) {
to_send = contig_space;
}
void* dest_buf = rb->virt() + (rb->format().bytes_per_frame() * wr_ptr);
if (span.is_mute) {
output_producer_->FillWithSilence(dest_buf, to_send);
} else {
FX_DCHECK(buffer != nullptr);
auto job_buf_offset = offset * output_producer_->channels();
output_producer_->ProduceOutput(buffer + job_buf_offset, dest_buf, to_send);
}
size_t dest_buf_len = to_send * output_producer_->bytes_per_frame();
wav_writer_.Write(dest_buf, dest_buf_len);
wav_writer_.UpdateHeader();
zx_cache_flush(dest_buf, dest_buf_len, ZX_CACHE_FLUSH_DATA);
frames_left -= to_send;
offset += to_send;
}
frames_sent_ += offset;
}
void DriverOutput::FinishMixJob(const AudioOutput::FrameSpan& span, const float* buffer) {
TRACE_DURATION("audio", "DriverOutput::FinishMixJob", "start", span.start, "length", span.length,
"is_mute", span.is_mute);
WriteToRing(span, buffer);
if (VERBOSE_TIMING_DEBUG) {
auto now = async::Now(mix_domain().dispatcher());
int64_t output_frames_consumed = RefTimeToSafeWriteFrame(now);
int64_t playback_lead_end = frames_sent_ - output_frames_consumed;
int64_t playback_lead_start = playback_lead_end - span.length;
FX_LOGS(INFO) << "PLead [" << std::setw(4) << playback_lead_start << ", " << std::setw(4)
<< playback_lead_end << "]";
}
ScheduleNextLowWaterWakeup();
}
void DriverOutput::ApplyGainLimits(fuchsia::media::AudioGainInfo* in_out_info,
fuchsia::media::AudioGainValidFlags set_flags) {
TRACE_DURATION("audio", "DriverOutput::ApplyGainLimits");
// See the comment at the start of StartMixJob. The actual limits we set here
// are going to eventually depend on what our HW gain control capabilities
// are, and how we choose to apply them (based on policy)
FX_DCHECK(in_out_info != nullptr);
// We do not currently allow more than unity gain for audio outputs.
if (in_out_info->gain_db > 0.0) {
in_out_info->gain_db = 0;
}
// Audio outputs should never support AGC
in_out_info->flags &= ~(fuchsia::media::AudioGainInfoFlags::AGC_ENABLED);
}
void DriverOutput::ScheduleNextLowWaterWakeup() {
TRACE_DURATION("audio", "DriverOutput::ScheduleNextLowWaterWakeup");
// After filling up, we are "high water frames" ahead of the safe write
// pointer. Compute when this will have been reduced to low_water_frames_.
// This is when we want to wake up and repeat the mixing cycle.
//
// frames_sent_ is the total number of frames we have ever synthesized since
// starting. Subtracting low_water_frames_ from this will give us the
// absolute frame number at which we are only low_water_frames_ ahead of the
// safe write pointer. Running this backwards through the safe write
// pointer's reference clock <-> frame number function will tell us when it
// will be time to wake up.
int64_t low_water_frame_number = frames_sent_ - low_water_frames_;
auto low_water_ref_time = SafeWriteFrameToRefTime(low_water_frame_number);
auto low_water_mono_time = reference_clock().MonotonicTimeFromReferenceTime(low_water_ref_time);
SetNextSchedTimeMono(low_water_mono_time);
}
void DriverOutput::OnDriverInfoFetched() {
TRACE_DURATION("audio", "DriverOutput::OnDriverInfoFetched");
auto cleanup = fit::defer([this]() FXL_NO_THREAD_SAFETY_ANALYSIS {
state_ = State::Shutdown;
ShutdownSelf();
});
if (state_ != State::FetchingFormats) {
FX_LOGS(ERROR) << "Unexpected GetFormatsComplete while in state "
<< static_cast<uint32_t>(state_);
return;
}
zx_status_t res;
DeviceConfig::OutputDeviceProfile profile =
config().output_device_profile(driver()->persistent_unique_id());
float driver_gain_db = profile.driver_gain_db();
AudioDeviceSettings::GainState gain_state = {.gain_db = driver_gain_db, .muted = false};
driver()->SetGain(gain_state, AUDIO_SGF_GAIN_VALID | AUDIO_SGF_MUTE_VALID);
PipelineConfig pipeline_config = profile.pipeline_config();
uint32_t pref_fps = pipeline_config.frames_per_second();
uint32_t pref_chan = pipeline_config.channels();
fuchsia::media::AudioSampleFormat pref_fmt = kDefaultAudioFmt;
zx::duration min_rb_duration =
kDefaultHighWaterNsec + kDefaultMaxRetentionNsec + kDefaultRetentionGapNsec;
res = driver()->SelectBestFormat(&pref_fps, &pref_chan, &pref_fmt);
if (res != ZX_OK) {
FX_LOGS(ERROR) << "Output: cannot match a driver format to this request: " << pref_fps
<< " Hz, " << pref_chan << "-channel, sample format 0x" << std::hex
<< static_cast<uint32_t>(pref_fmt);
return;
}
auto format_result = Format::Create(fuchsia::media::AudioStreamType{
.sample_format = pref_fmt,
.channels = pref_chan,
.frames_per_second = pref_fps,
});
if (format_result.is_error()) {
FX_LOGS(ERROR) << "Driver format is invalid";
return;
}
auto& format = format_result.value();
// Update our pipeline to produce audio in the compatible format.
if (pipeline_config.frames_per_second() != pref_fps) {
FX_LOGS(WARNING) << "Hardware does not support the requested rate of "
<< pipeline_config.root().output_rate << " fps; hardware will run at "
<< pref_fps << " fps";
pipeline_config.mutable_root().output_rate = pref_fps;
}
if (pipeline_config.channels() != pref_chan) {
FX_LOGS(WARNING) << "Hardware does not support the requested channelization of "
<< pipeline_config.channels() << " channels; hardware will run at "
<< pref_chan << " channels";
pipeline_config.mutable_root().output_channels = pref_chan;
// Some effects may perform rechannelization. If the hardware does not support the
// channelization with rechannelization effects we clear all effects on the final stage. This
// is a compromise in being robust and gracefully handling misconfiguration.
for (const auto& effect : pipeline_config.root().effects) {
if (effect.output_channels && effect.output_channels != pref_chan) {
FX_LOGS(ERROR) << "Removing effects on the root stage due to unsupported channelization";
pipeline_config.mutable_root().effects.clear();
break;
}
}
}
FX_DCHECK(pipeline_config.frames_per_second() == pref_fps);
FX_DCHECK(pipeline_config.channels() == pref_chan);
// Update the AudioDevice |config_| with the updated |pipeline_config|.
// Only |frames_per_second| and |channels| were potentially updated in |pipeline_config|, so it is
// not necessary to UpdateDeviceProfile() to consequently reconstruct the OutputPipeline.
auto updated_profile = DeviceConfig::OutputDeviceProfile(
profile.eligible_for_loopback(), profile.supported_usages(), profile.volume_curve(),
profile.independent_volume_control(), pipeline_config, profile.driver_gain_db());
DeviceConfig updated_config = config();
updated_config.SetOutputDeviceProfile(driver()->persistent_unique_id(), updated_profile);
set_config(updated_config);
// Select our output producer
output_producer_ = OutputProducer::Select(format.stream_type());
if (!output_producer_) {
FX_LOGS(ERROR) << "Output: OutputProducer cannot support this request: " << pref_fps << " Hz, "
<< pref_chan << "-channel, sample format 0x" << std::hex
<< static_cast<uint32_t>(pref_fmt);
return;
}
// Start the process of configuring our driver
res = driver()->Configure(format, min_rb_duration);
if (res != ZX_OK) {
FX_LOGS(ERROR) << "Output: failed to configure driver for: " << pref_fps << " Hz, " << pref_chan
<< "-channel, sample format 0x" << std::hex << static_cast<uint32_t>(pref_fmt)
<< " (res " << std::dec << res << ")";
return;
}
if constexpr (kEnableFinalMixWavWriter) {
std::string file_name_ = kDefaultWavFilePathName;
uint32_t instance_count = final_mix_instance_num_.fetch_add(1);
file_name_ += (std::to_string(instance_count) + kWavFileExtension);
wav_writer_.Initialize(file_name_.c_str(), pref_fmt, pref_chan, pref_fps,
format.bytes_per_frame() * 8 / pref_chan);
}
// Success; now wait until configuration completes.
state_ = State::Configuring;
cleanup.cancel();
}
void DriverOutput::OnDriverConfigComplete() {
TRACE_DURATION("audio", "DriverOutput::OnDriverConfigComplete");
auto cleanup = fit::defer([this]() FXL_NO_THREAD_SAFETY_ANALYSIS {
state_ = State::Shutdown;
ShutdownSelf();
});
if (state_ != State::Configuring) {
FX_LOGS(ERROR) << "Unexpected ConfigComplete while in state " << static_cast<uint32_t>(state_);
return;
}
// Driver is configured, we have all the needed info to compute the presentation
// delay for this output.
SetPresentationDelay(driver()->external_delay() + driver()->fifo_depth_duration() +
kDefaultHighWaterNsec);
// Fill our brand new ring buffer with silence
FX_DCHECK(driver_writable_ring_buffer() != nullptr);
const auto& rb = *driver_writable_ring_buffer();
FX_DCHECK(output_producer_ != nullptr);
FX_DCHECK(rb.virt() != nullptr);
output_producer_->FillWithSilence(rb.virt(), rb.frames());
// Start the ring buffer running
//
// TODO(fxbug.dev/13292) : Don't actually start things up here. We should start only when we have
// clients with work to do, and we should stop when we have no work to do.
zx_status_t res = driver()->Start();
if (res != ZX_OK) {
FX_PLOGS(ERROR, res) << "Failed to start ring buffer";
return;
}
// Start monitoring plug state.
res = driver()->SetPlugDetectEnabled(true);
if (res != ZX_OK) {
FX_PLOGS(ERROR, res) << "Failed to enable plug detection";
return;
}
// Success
state_ = State::Starting;
cleanup.cancel();
}
void DriverOutput::OnDriverStartComplete() {
TRACE_DURATION("audio", "DriverOutput::OnDriverStartComplete");
if (state_ != State::Starting) {
FX_LOGS(ERROR) << "Unexpected StartComplete while in state " << static_cast<uint32_t>(state_);
return;
}
reporter().SetDriverInfo(*driver());
// Set up the mix task in the AudioOutput.
//
// TODO(fxbug.dev/39886): The intermediate buffer probably does not need to be as large as the
// entire ring buffer. Consider limiting this to be something only slightly larger than a nominal
// mix job.
auto format = driver()->GetFormat();
FX_DCHECK(format);
SetupMixTask(config().output_device_profile(driver()->persistent_unique_id()),
driver_writable_ring_buffer()->frames(),
driver_ref_time_to_frac_presentation_frame());
// Tell AudioDeviceManager we are ready to be an active audio device.
ActivateSelf();
// Compute low_water_frames_. low_water_frames_ is minimum the number of
// frames ahead of the safe write position we ever want to be. When we hit
// the point where we are only this number of frames ahead of the safe write
// position, we need to wake up and fill up to our high water mark.
const TimelineRate rate = FramesPerRefTick();
low_water_frames_ = rate.Scale(kDefaultLowWaterNsec.get());
// We started with a buffer full of silence. Set up our bookkeeping so we
// consider ourselves to have generated and sent up to our low-water mark's
// worth of silence already beyond the where the current safe write frame
// in the ring buffer is.
//
// We read from async::Now and convert to reference time to simplify unit
// tests that mock out time using async::Now.
//
// TODO(57377): Keep reference clocks in sync with mono time under test.
auto mono_time = async::Now(mix_domain().dispatcher());
auto ref_time = reference_clock().ReferenceTimeFromMonotonicTime(mono_time);
int64_t output_frames_consumed = RefTimeToSafeWriteFrame(ref_time);
frames_sent_ = output_frames_consumed + low_water_frames_;
if (VERBOSE_TIMING_DEBUG) {
auto fd_frames = driver()->fifo_depth_frames();
FX_LOGS(INFO) << "Audio output: FIFO depth (" << fd_frames << " frames " << std::fixed
<< std::setprecision(3) << rate.Inverse().Scale(fd_frames) / 1000000.0
<< " mSec) Low Water (" << frames_sent_ << " frames " << std::fixed
<< std::setprecision(3) << rate.Inverse().Scale(frames_sent_) / 1000000.0
<< " mSec)";
}
reporter().StartSession(zx::clock::get_monotonic());
state_ = State::Started;
Process();
}
} // namespace media::audio