blob: 9d71ae8851955a943e63029e90113e133a6fb95c [file] [log] [blame]
// Copyright 2019 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/drivers/virtual_audio/virtual_audio_device_impl.h"
#include <lib/zx/clock.h>
#include <memory>
#include <ddk/debug.h>
#include "src/media/audio/drivers/virtual_audio/virtual_audio_stream.h"
namespace virtual_audio {
class VirtualAudioStreamIn;
class VirtualAudioStreamOut;
// static methods
std::unique_ptr<VirtualAudioDeviceImpl> VirtualAudioDeviceImpl::Create(
VirtualAudioControlImpl* owner, bool is_input) {
return std::unique_ptr<VirtualAudioDeviceImpl>(new VirtualAudioDeviceImpl(owner, is_input));
}
// Don't initialize here or in ctor; do it all in Init() so ResetConfiguration has same effect.
VirtualAudioDeviceImpl::VirtualAudioDeviceImpl(VirtualAudioControlImpl* owner, bool is_input)
: owner_(owner), is_input_(is_input) {
ZX_DEBUG_ASSERT(owner_);
Init();
}
// If we have not already destroyed our child stream, do so now.
VirtualAudioDeviceImpl::~VirtualAudioDeviceImpl() { RemoveStream(); }
// Initialize (or reset) a device's static configuration.
void VirtualAudioDeviceImpl::Init() {
device_name_ = kDefaultDeviceName;
mfr_name_ = kDefaultManufacturerName;
prod_name_ = kDefaultProductName;
memcpy(unique_id_, kDefaultUniqueId, sizeof(unique_id_));
clock_domain_ = kDefaultClockDomain;
clock_rate_adjustment_ = kDefaultClockRateAdjustment;
// By default, we support one basic format range (stereo 16-bit 48kHz)
supported_formats_.clear();
supported_formats_.push_back(kDefaultFormatRange);
fifo_depth_ = kDefaultFifoDepthBytes;
external_delay_nsec_ = kDefaultExternalDelayNsec;
min_buffer_frames_ = kDefaultMinBufferFrames;
max_buffer_frames_ = kDefaultMaxBufferFrames;
modulo_buffer_frames_ = kDefaultModuloBufferFrames;
cur_gain_state_ = kDefaultGainState;
hardwired_ = kDefaultHardwired;
async_plug_notify_ = kDefaultPlugCanNotify;
plugged_ = kDefaultPlugged;
// The time this Configuration was created / reset.
plug_time_ = zx::clock::get_monotonic().get();
override_notification_frequency_ = false;
notifications_per_ring_ = 0;
}
// Receive and cache a virtualaudio::Input binding, forwarded from the virtual_audio_service.
// ControlImpl has already associated us with the binding: when it closes we will auto-destruct.
void VirtualAudioDeviceImpl::SetBinding(
fidl::Binding<fuchsia::virtualaudio::Input,
std::unique_ptr<virtual_audio::VirtualAudioDeviceImpl>>* binding) {
ZX_ASSERT(is_input_);
ZX_DEBUG_ASSERT(output_binding_ == nullptr);
input_binding_ = binding;
ZX_DEBUG_ASSERT(input_binding_->is_bound());
}
// Receive and cache a virtualaudio::Output binding, forwarded from the virtual_audio_service.
// ControlImpl has already associated us with the binding: when it closes we will auto-destruct.
void VirtualAudioDeviceImpl::SetBinding(
fidl::Binding<fuchsia::virtualaudio::Output,
std::unique_ptr<virtual_audio::VirtualAudioDeviceImpl>>* binding) {
ZX_ASSERT(!is_input_);
ZX_DEBUG_ASSERT(input_binding_ == nullptr);
output_binding_ = binding;
ZX_DEBUG_ASSERT(output_binding_->is_bound());
}
// In response to an Input::Add or Output::Add call, create the stream (activate the device).
bool VirtualAudioDeviceImpl::CreateStream(zx_device_t* devnode) {
stream_ = VirtualAudioStream::CreateStream(this, devnode, is_input_);
return (stream_ != nullptr);
}
// Remove our child stream by calling its DdkUnbind. This may already have occurred; check for null.
//
// TODO(mpuryear): This may not safely unwind in all cases: it makes some threading assumptions that
// cannot necessarily be enforced. But until fxbug.dev/33258 is addressed, the current VAD code
// appears to be safe -- all RemoveStream callers are on the devhost primary thread:
// ~VirtualAudioDeviceImpl from DevHost removing parent,
// ~VirtualAudioDeviceImpl from Input|Output FIDL channel disconnecting
// fuchsia.virtualaudio.Control.Disable
// fuchsia.virtualaudio.Input|Output.Remove
void VirtualAudioDeviceImpl::RemoveStream() {
if (stream_ != nullptr) {
// Clear any queued dispatcher tasks, and no longer accept new tasks.
task_queue_->StopAndClear();
// This bool tells the stream that Unbind originates from us (parent), so don't call us back.
stream_->shutdown_by_parent_ = true;
// Shutdown won't return until any in-flight calls to PostToDispatcher are quiesced.
// Shutdown deactivates its execution domain, and all PostToDispatcher calls are
// made from this execution domain). This guarantee is critical, because Add changes what
// task_queue_ points to. If calls to PostToDispatcher WERE still outstanding, then it would be
// possible that Add could be called before task_queue_->Enqueue runs, causing us to erroneously
// run a task associated with the older stream, in the context of the new stream.
stream_->Shutdown();
// Request the device be unbound.
stream_->DdkAsyncRemove();
// Now that the stream has done its shutdown, we release our reference.
stream_ = nullptr;
}
// bindings are reset in the VirtualAudioDeviceImpl dtor
}
// In most cases, stream DdkUnbind is called by us (the parent DeviceImpl), above in RemoveStream.
// In the other cases, this RemoveStream is eventually called, but we should not RE-call DdkUnbind
// at that point. To cover those other cases, this ClearStream method allows the child stream to
// signal to its parent that it has gone away. Conversely, the flag shutdown_by_parent_ provides the
// device->stream signal in the other cases (such as when RemoveStream is called during the device
// dtor), to let the stream know that it need not and should not call us.
void VirtualAudioDeviceImpl::ClearStream() {
// Clear any queued dispatcher tasks, and no longer accept new tasks.
task_queue_->StopAndClear();
// Forget about this stream, but don't do more right now -- our RemoveStream will be called later.
stream_ = nullptr;
}
// Use closure_queue to post a dispatcher task -- cancellable if stream or device is destroyed.
void VirtualAudioDeviceImpl::PostToDispatcher(fit::closure task_to_post) {
// We know task_queue_ isn't changing which ClosureQueue it's holding as this line is running,
// because RemoveStream always runs on the same thread as Add (the main devhost thread, the FIDL
// dispatch thread, the ClosureQueue's destination thread), and RemoveStream quiesces all calls to
// PostToDispatcher before it returns (and thus before Add changes what task_queue_ points to).
task_queue_->Enqueue(std::move(task_to_post));
}
//
// virtualaudio::Configuration implementation
//
void VirtualAudioDeviceImpl::SetDeviceName(std::string device_name) {
device_name_ = device_name;
WarnActiveStreamNotAffected(__func__);
}
void VirtualAudioDeviceImpl::SetManufacturer(std::string manufacturer_name) {
mfr_name_ = manufacturer_name;
WarnActiveStreamNotAffected(__func__);
}
void VirtualAudioDeviceImpl::SetProduct(std::string product_name) {
prod_name_ = product_name;
WarnActiveStreamNotAffected(__func__);
}
void VirtualAudioDeviceImpl::SetUniqueId(std::array<uint8_t, 16> unique_id) {
memcpy(unique_id_, unique_id.data(), sizeof(unique_id_));
WarnActiveStreamNotAffected(__func__);
}
// After creation or reset, one default format range is always available.
void VirtualAudioDeviceImpl::AddFormatRange(uint32_t format_flags, uint32_t min_rate,
uint32_t max_rate, uint8_t min_chans, uint8_t max_chans,
uint16_t rate_family_flags) {
audio_stream_format_range_t range = {.sample_formats = format_flags,
.min_frames_per_second = min_rate,
.max_frames_per_second = max_rate,
.min_channels = min_chans,
.max_channels = max_chans,
.flags = rate_family_flags};
supported_formats_.push_back(range);
WarnActiveStreamNotAffected(__func__);
}
void VirtualAudioDeviceImpl::ClearFormatRanges() {
supported_formats_.clear();
WarnActiveStreamNotAffected(__func__);
}
void VirtualAudioDeviceImpl::WarnActiveStreamNotAffected(const char* func_name) {
if (stream_ != nullptr) {
zxlogf(WARNING, "%s updates the static config, but has no effect on existing streams",
func_name);
}
}
void VirtualAudioDeviceImpl::SetClockProperties(int32_t clock_domain,
int32_t initial_rate_adjustment_ppm) {
clock_domain_ = clock_domain;
clock_rate_adjustment_ = initial_rate_adjustment_ppm;
WarnActiveStreamNotAffected(__func__);
}
void VirtualAudioDeviceImpl::SetFifoDepth(uint32_t fifo_depth_bytes) {
fifo_depth_ = fifo_depth_bytes;
WarnActiveStreamNotAffected(__func__);
}
void VirtualAudioDeviceImpl::SetExternalDelay(zx_duration_t external_delay) {
external_delay_nsec_ = external_delay;
WarnActiveStreamNotAffected(__func__);
}
void VirtualAudioDeviceImpl::SetRingBufferRestrictions(uint32_t min_frames, uint32_t max_frames,
uint32_t modulo_frames) {
ZX_DEBUG_ASSERT(min_frames <= max_frames);
ZX_DEBUG_ASSERT(min_frames % modulo_frames == 0);
ZX_DEBUG_ASSERT(max_frames % modulo_frames == 0);
min_buffer_frames_ = min_frames;
max_buffer_frames_ = max_frames;
modulo_buffer_frames_ = modulo_frames;
WarnActiveStreamNotAffected(__func__);
}
void VirtualAudioDeviceImpl::SetGainProperties(float min_gain_db, float max_gain_db,
float gain_step_db, float current_gain_db,
bool can_mute, bool current_mute, bool can_agc,
bool current_agc) {
cur_gain_state_ = {.cur_mute = current_mute,
.cur_agc = current_agc,
.cur_gain = current_gain_db,
.can_mute = can_mute,
.can_agc = can_agc,
.min_gain = min_gain_db,
.max_gain = max_gain_db,
.gain_step = gain_step_db};
WarnActiveStreamNotAffected(__func__);
}
void VirtualAudioDeviceImpl::SetPlugProperties(zx_time_t plug_change_time, bool plugged,
bool hardwired, bool can_notify) {
plug_time_ = plug_change_time;
plugged_ = plugged;
hardwired_ = hardwired;
async_plug_notify_ = can_notify;
WarnActiveStreamNotAffected(__func__);
}
void VirtualAudioDeviceImpl::ResetConfiguration() {
Init();
WarnActiveStreamNotAffected(__func__);
}
//
// virtualaudio::Device implementation
//
// Create a virtual audio device using the currently-specified configuration.
void VirtualAudioDeviceImpl::Add() {
if (!owner_->enabled()) {
zxlogf(WARNING, "%s: Disabled, cannot add stream", __func__);
return;
}
if (stream_ != nullptr) {
zxlogf(WARNING, "%s: %p already has stream %p", __func__, this, stream_.get());
return;
}
// This ClosureQueue is created when stream is created), deactivated (via StopAndClear) when
// stream is removed, and synchronized by being on devhost dispatcher thread (our FIDL thread).
// DeviceImpl and Stream both call PostToDispatcher, and all are quiesced before we get here.
// DeviceImpl makes those calls on the FIDL thread (task_queue_'s destination thread); all are
// enqueued and subsequently discarded (via StopAndClear) upon RemoveStream. Stream makes those
// calls in its execution domain, which is synchronously deactivated during its DdkUnbind.
task_queue_.emplace(owner_->dispatcher(), thrd_current());
if (!CreateStream(owner_->dev_node())) {
zxlogf(ERROR, "CreateStream failed");
task_queue_->StopAndClear();
return;
}
}
// Remove the associated virtual audio device.
void VirtualAudioDeviceImpl::Remove() {
if (!owner_->enabled()) {
zxlogf(WARNING, "%s: Disabled, no streams for removal", __func__);
ZX_DEBUG_ASSERT(stream_ == nullptr);
return;
}
if (stream_ == nullptr) {
zxlogf(WARNING, "%s: %p has no stream to remove", __func__, this);
return;
}
// If stream_ exists, null our copy and call SimpleAudioStream::DdkUnbind (which eventually calls
// ShutdownHook and re-nulls). We need this because stream terminations come either from "device"
// (direct DdkUnbind call), or from "parent" (Control::Disable, Device::Remove, ~DeviceImpl).
RemoveStream();
}
void VirtualAudioDeviceImpl::GetFormat(
fuchsia::virtualaudio::Device::GetFormatCallback format_callback) {
if (stream_ == nullptr) {
zxlogf(WARNING, "%s: %p has no stream for this request", __func__, this);
return;
}
stream_->EnqueueFormatRequest(std::move(format_callback));
}
// Deliver SetFormat notification on binding's thread, if binding is valid.
void VirtualAudioDeviceImpl::NotifySetFormat(uint32_t frames_per_second, uint32_t sample_format,
uint32_t num_channels, zx_duration_t external_delay) {
PostToDispatcher([this, frames_per_second, sample_format, num_channels, external_delay]() {
if (input_binding_ && input_binding_->is_bound()) {
input_binding_->events().OnSetFormat(frames_per_second, sample_format, num_channels,
external_delay);
} else if (output_binding_ && output_binding_->is_bound()) {
output_binding_->events().OnSetFormat(frames_per_second, sample_format, num_channels,
external_delay);
}
});
}
void VirtualAudioDeviceImpl::GetGain(fuchsia::virtualaudio::Device::GetGainCallback gain_callback) {
if (stream_ == nullptr) {
zxlogf(WARNING, "%s: %p has no stream for this request", __func__, this);
return;
}
stream_->EnqueueGainRequest(std::move(gain_callback));
}
// Deliver SetGain notification on binding's thread, if binding is valid.
void VirtualAudioDeviceImpl::NotifySetGain(bool current_mute, bool current_agc,
float current_gain_db) {
PostToDispatcher([this, current_mute, current_agc, current_gain_db]() {
if (input_binding_ && input_binding_->is_bound()) {
input_binding_->events().OnSetGain(current_mute, current_agc, current_gain_db);
} else if (output_binding_ && output_binding_->is_bound()) {
output_binding_->events().OnSetGain(current_mute, current_agc, current_gain_db);
}
});
}
void VirtualAudioDeviceImpl::GetBuffer(
fuchsia::virtualaudio::Device::GetBufferCallback buffer_callback) {
if (stream_ == nullptr) {
zxlogf(WARNING, "%s: %p has no stream for this request", __func__, this);
return;
}
stream_->EnqueueBufferRequest(std::move(buffer_callback));
}
// Deliver SetBuffer notification on binding's thread, if binding is valid.
void VirtualAudioDeviceImpl::NotifyBufferCreated(zx::vmo ring_buffer_vmo,
uint32_t num_ring_buffer_frames,
uint32_t notifications_per_ring) {
PostToDispatcher([this, ring_buffer_vmo = std::move(ring_buffer_vmo), num_ring_buffer_frames,
notifications_per_ring]() mutable {
if (input_binding_ && input_binding_->is_bound()) {
input_binding_->events().OnBufferCreated(std::move(ring_buffer_vmo), num_ring_buffer_frames,
notifications_per_ring);
} else if (output_binding_ && output_binding_->is_bound()) {
output_binding_->events().OnBufferCreated(std::move(ring_buffer_vmo), num_ring_buffer_frames,
notifications_per_ring);
}
});
}
// Override AudioCore's systemwide position notification cadence, in favor of a per-stream
// notification cadence. Update the static config, and if active, tell device to dynamically change.
void VirtualAudioDeviceImpl::SetNotificationFrequency(uint32_t notifications_per_ring) {
// This is a DeviceImpl property (stream has a property with same name)
override_notification_frequency_ = true;
notifications_per_ring_ = notifications_per_ring;
if (stream_ != nullptr) {
stream_->EnqueueNotificationOverride(notifications_per_ring);
}
}
// Deliver Start notification on binding's thread, if binding is valid.
void VirtualAudioDeviceImpl::NotifyStart(zx_time_t start_time) {
PostToDispatcher([this, start_time]() {
if (is_input_) {
ZX_ASSERT(input_binding_ && input_binding_->is_bound());
input_binding_->events().OnStart(start_time);
} else {
ZX_ASSERT(output_binding_ && output_binding_->is_bound());
output_binding_->events().OnStart(start_time);
}
});
}
// Deliver Stop notification on binding's thread, if binding is valid.
void VirtualAudioDeviceImpl::NotifyStop(zx_time_t stop_time, uint32_t ring_buffer_position) {
PostToDispatcher([this, stop_time, ring_buffer_position]() {
if (is_input_) {
ZX_ASSERT(input_binding_ && input_binding_->is_bound());
input_binding_->events().OnStop(stop_time, ring_buffer_position);
} else {
ZX_ASSERT(output_binding_ && output_binding_->is_bound());
output_binding_->events().OnStop(stop_time, ring_buffer_position);
}
});
}
void VirtualAudioDeviceImpl::GetPosition(
fuchsia::virtualaudio::Device::GetPositionCallback position_callback) {
if (stream_ == nullptr) {
zxlogf(WARNING, "%s: %p has no stream for this request", __func__, this);
return;
}
stream_->EnqueuePositionRequest(std::move(position_callback));
}
// Deliver Position notification on binding's thread, if binding is valid.
void VirtualAudioDeviceImpl::NotifyPosition(zx_time_t monotonic_time,
uint32_t ring_buffer_position) {
PostToDispatcher([this, monotonic_time, ring_buffer_position]() {
if (is_input_) {
ZX_ASSERT(input_binding_ && input_binding_->is_bound());
input_binding_->events().OnPositionNotify(monotonic_time, ring_buffer_position);
} else {
ZX_ASSERT(output_binding_ && output_binding_->is_bound());
output_binding_->events().OnPositionNotify(monotonic_time, ring_buffer_position);
}
});
}
// Change the plug state on-the-fly for this active virtual audio device.
void VirtualAudioDeviceImpl::ChangePlugState(zx_time_t plug_change_time, bool plugged) {
// Update static config, and tell (if present) stream to dynamically change.
plug_time_ = plug_change_time;
plugged_ = plugged;
if (!owner_->enabled()) {
zxlogf(WARNING, "%s: Disabled, cannot change plug state; changing only the static config",
__func__);
return;
}
if (stream_ == nullptr) {
zxlogf(WARNING, "%s: %p has no stream; cannot change dynamic plug state", __func__, this);
return;
}
stream_->EnqueuePlugChange(plugged);
}
// Change the plug state on-the-fly for this active virtual audio device.
void VirtualAudioDeviceImpl::AdjustClockRate(int32_t ppm_from_monotonic) {
// Update static config, and tell (if present) stream to dynamically change.
clock_rate_adjustment_ = ppm_from_monotonic;
if (!owner_->enabled()) {
zxlogf(WARNING, "%s: Disabled, cannot adjust clock rate; changing only the static config",
__func__);
return;
}
if (stream_ == nullptr) {
zxlogf(WARNING, "%s: %p has no stream; cannot change clock rate", __func__, this);
return;
}
stream_->EnqueueClockRateAdjustment(ppm_from_monotonic);
}
} // namespace virtual_audio