blob: 266503aa1d1decac1cfdd6ce70259cd8b03bb86b [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_stream.h"
#include <lib/zx/clock.h>
#include <cmath>
#include <ddk/debug.h>
#include "src/media/audio/drivers/virtual_audio/virtual_audio_device_impl.h"
#include "src/media/audio/drivers/virtual_audio/virtual_audio_stream_in.h"
#include "src/media/audio/drivers/virtual_audio/virtual_audio_stream_out.h"
namespace virtual_audio {
// static
fbl::RefPtr<VirtualAudioStream> VirtualAudioStream::CreateStream(VirtualAudioDeviceImpl* owner,
zx_device_t* devnode,
bool is_input) {
if (is_input) {
return audio::SimpleAudioStream::Create<VirtualAudioStreamIn>(owner, devnode);
} else {
return audio::SimpleAudioStream::Create<VirtualAudioStreamOut>(owner, devnode);
}
}
zx_status_t VirtualAudioStream::Init() {
if (!strlcpy(device_name_, parent_->device_name_.c_str(), sizeof(device_name_))) {
return ZX_ERR_INTERNAL;
}
if (!strlcpy(mfr_name_, parent_->mfr_name_.c_str(), sizeof(mfr_name_))) {
return ZX_ERR_INTERNAL;
}
if (!strlcpy(prod_name_, parent_->prod_name_.c_str(), sizeof(prod_name_))) {
return ZX_ERR_INTERNAL;
}
memcpy(unique_id_.data, parent_->unique_id_, sizeof(unique_id_.data));
supported_formats_.reset();
for (auto range : parent_->supported_formats_) {
supported_formats_.push_back(range);
}
fifo_depth_ = parent_->fifo_depth_;
external_delay_nsec_ = parent_->external_delay_nsec_;
clock_domain_ = parent_->clock_domain_;
max_buffer_frames_ = parent_->max_buffer_frames_;
min_buffer_frames_ = parent_->min_buffer_frames_;
modulo_buffer_frames_ = parent_->modulo_buffer_frames_;
cur_gain_state_ = parent_->cur_gain_state_;
audio_pd_notify_flags_t plug_flags = 0;
if (parent_->hardwired_) {
plug_flags |= AUDIO_PDNF_HARDWIRED;
}
if (parent_->async_plug_notify_) {
plug_flags |= AUDIO_PDNF_CAN_NOTIFY;
}
if (parent_->plugged_) {
plug_flags |= AUDIO_PDNF_PLUGGED;
}
SetInitialPlugState(plug_flags);
using_alt_notifications_ = parent_->override_notification_frequency_;
if (parent_->override_notification_frequency_) {
alt_notifications_per_ring_ = parent_->notifications_per_ring_;
}
return ZX_OK;
}
void VirtualAudioStream::EnqueuePlugChange(bool plugged) {
{
fbl::AutoLock lock(&wakeup_queue_lock_);
PlugType plug_change = (plugged ? PlugType::Plug : PlugType::Unplug);
plug_queue_.push_back(plug_change);
}
plug_change_wakeup_.Post(dispatcher());
}
void VirtualAudioStream::EnqueueGainRequest(
fuchsia::virtualaudio::Device::GetGainCallback gain_callback) {
{
fbl::AutoLock lock(&wakeup_queue_lock_);
gain_queue_.push_back(std::move(gain_callback));
}
gain_request_wakeup_.Post(dispatcher());
}
void VirtualAudioStream::EnqueueFormatRequest(
fuchsia::virtualaudio::Device::GetFormatCallback format_callback) {
{
fbl::AutoLock lock(&wakeup_queue_lock_);
format_queue_.push_back(std::move(format_callback));
}
format_request_wakeup_.Post(dispatcher());
}
void VirtualAudioStream::EnqueueBufferRequest(
fuchsia::virtualaudio::Device::GetBufferCallback buffer_callback) {
{
fbl::AutoLock lock(&wakeup_queue_lock_);
buffer_queue_.push_back(std::move(buffer_callback));
}
buffer_request_wakeup_.Post(dispatcher());
}
void VirtualAudioStream::EnqueuePositionRequest(
fuchsia::virtualaudio::Device::GetPositionCallback position_callback) {
{
fbl::AutoLock lock(&wakeup_queue_lock_);
position_queue_.push_back(std::move(position_callback));
}
position_request_wakeup_.Post(dispatcher());
}
void VirtualAudioStream::EnqueueNotificationOverride(uint32_t notifications_per_ring) {
{
fbl::AutoLock lock(&wakeup_queue_lock_);
notifs_queue_.push_back(notifications_per_ring);
}
set_notifications_wakeup_.Post(dispatcher());
}
void VirtualAudioStream::HandlePlugChanges() {
audio::ScopedToken t(
domain_token()); // This method handles posts to plug_change_wakeup_ sent to dispatcher().
while (true) {
PlugType plug_change;
if (fbl::AutoLock lock(&wakeup_queue_lock_); !plug_queue_.empty()) {
plug_change = plug_queue_.front();
plug_queue_.pop_front();
} else {
break;
}
HandlePlugChange(plug_change);
}
}
void VirtualAudioStream::HandlePlugChange(PlugType plug_change) {
switch (plug_change) {
case PlugType::Plug:
SetPlugState(true);
break;
case PlugType::Unplug:
SetPlugState(false);
break;
// Intentionally omitting default, so new enums surface a logic error.
}
}
void VirtualAudioStream::HandleGainRequests() {
audio::ScopedToken t(
domain_token()); // This method handles posts to gain_request_wakeup_ sent to dispatcher().
while (true) {
bool current_mute, current_agc;
float current_gain_db;
fuchsia::virtualaudio::Device::GetGainCallback gain_callback;
if (fbl::AutoLock lock(&wakeup_queue_lock_); !gain_queue_.empty()) {
current_mute = cur_gain_state_.cur_mute;
current_agc = cur_gain_state_.cur_agc;
current_gain_db = cur_gain_state_.cur_gain;
gain_callback = std::move(gain_queue_.front());
gain_queue_.pop_front();
} else {
break;
}
parent_->PostToDispatcher(
[gain_callback = std::move(gain_callback), current_mute, current_agc, current_gain_db]() {
gain_callback(current_mute, current_agc, current_gain_db);
});
}
}
void VirtualAudioStream::HandleFormatRequests() {
audio::ScopedToken t(
domain_token()); // This method handles posts to format_request_wakeup_ sent to dispatcher().
while (true) {
uint32_t frames_per_second, sample_format, num_channels;
zx_duration_t external_delay;
fuchsia::virtualaudio::Device::GetFormatCallback format_callback;
if (fbl::AutoLock lock(&wakeup_queue_lock_); !format_queue_.empty()) {
frames_per_second = frame_rate_;
sample_format = sample_format_;
num_channels = num_channels_;
external_delay = external_delay_nsec_;
format_callback = std::move(format_queue_.front());
format_queue_.pop_front();
} else {
break;
}
if (frames_per_second == 0) {
zxlogf(WARN, "Format is not set - should not be calling GetFormat");
return;
}
parent_->PostToDispatcher([format_callback = std::move(format_callback), frames_per_second,
sample_format, num_channels, external_delay]() {
format_callback(frames_per_second, sample_format, num_channels, external_delay);
});
}
}
void VirtualAudioStream::HandleBufferRequests() {
audio::ScopedToken t(
domain_token()); // This method handles posts to buffer_request_wakeup_ sent to dispatcher().
while (true) {
zx_status_t status;
zx::vmo duplicate_ring_buffer_vmo;
uint32_t num_ring_buffer_frames;
uint32_t notifications_per_ring;
fuchsia::virtualaudio::Device::GetBufferCallback buffer_callback;
if (fbl::AutoLock lock(&wakeup_queue_lock_); !buffer_queue_.empty()) {
if (!ring_buffer_vmo_.is_valid()) {
zxlogf(WARN, "Buffer is not set - should not be retrieving ring buffer");
return;
}
status = ring_buffer_vmo_.duplicate(
ZX_RIGHT_TRANSFER | ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP,
&duplicate_ring_buffer_vmo);
num_ring_buffer_frames = num_ring_buffer_frames_;
notifications_per_ring = notifications_per_ring_;
buffer_callback = std::move(buffer_queue_.front());
buffer_queue_.pop_front();
} else {
break;
}
if (status != ZX_OK) {
zxlogf(ERROR, "%s failed to duplicate VMO handle - %d", __func__, status);
return;
}
parent_->PostToDispatcher([buffer_callback = std::move(buffer_callback),
rb_vmo = std::move(duplicate_ring_buffer_vmo),
num_ring_buffer_frames, notifications_per_ring]() mutable {
buffer_callback(std::move(rb_vmo), num_ring_buffer_frames, notifications_per_ring);
});
}
}
void VirtualAudioStream::HandlePositionRequests() {
audio::ScopedToken t(domain_token()); // This method handles posts to position_request_wakeup_
// sent to dispatcher().
while (true) {
zx::time start_time;
uint32_t num_rb_frames, frame_size, frame_rate;
fuchsia::virtualaudio::Device::GetPositionCallback position_callback;
if (fbl::AutoLock lock(&wakeup_queue_lock_); !position_queue_.empty()) {
start_time = start_time_;
num_rb_frames = num_ring_buffer_frames_;
frame_size = frame_size_;
frame_rate = frame_rate_;
position_callback = std::move(position_queue_.front());
position_queue_.pop_front();
} else {
break;
}
if (start_time.get() == 0) {
zxlogf(WARN, "Stream is not started -- should not be calling GetPosition");
return;
}
zx::time now = zx::clock::get_monotonic();
zx::duration running_duration = now - start_time;
uint64_t frames = (running_duration * frame_rate) / zx::sec(1);
uint32_t ring_buffer_position = (frames % num_rb_frames) * frame_size;
zx_time_t time_for_position = now.get();
parent_->PostToDispatcher(
[position_callback = std::move(position_callback), time_for_position,
ring_buffer_position]() { position_callback(time_for_position, ring_buffer_position); });
}
}
void VirtualAudioStream::HandleSetNotifications() {
audio::ScopedToken t(domain_token()); // This method handles posts to set_notifications_wakeup_
// sent to dispatcher().
while (true) {
if (fbl::AutoLock lock(&wakeup_queue_lock_); !notifs_queue_.empty()) {
alt_notifications_per_ring_ = notifs_queue_.front();
notifs_queue_.pop_front();
// If alternate cadence is same as "official" cadence, use it instead.
if (alt_notifications_per_ring_ == notifications_per_ring_) {
using_alt_notifications_ = false;
continue;
}
using_alt_notifications_ = true;
if (alt_notifications_per_ring_ == 0) {
alt_notification_period_ = zx::duration(0);
} else {
alt_notification_period_ = zx::duration((zx::sec(1) * num_ring_buffer_frames_) /
(frame_rate_ * alt_notifications_per_ring_));
}
} else {
break;
}
}
if (using_alt_notifications_ && (alt_notification_period_.get() > 0)) {
target_alt_notification_time_ = zx::clock::get_monotonic();
alt_notify_timer_.PostForTime(dispatcher(), target_alt_notification_time_);
} else {
target_alt_notification_time_ = zx::time(0);
alt_notify_timer_.Cancel();
}
}
// Upon success, drivers should return a valid VMO with appropriate permissions (READ | MAP |
// TRANSFER for inputs, WRITE as well for outputs) as well as reporting the total number of usable
// frames in the ring.
//
// Format must already be set: a ring buffer channel (over which this command arrived) is provided
// as the return value from a successful SetFormat call.
zx_status_t VirtualAudioStream::GetBuffer(const audio::audio_proto::RingBufGetBufferReq& req,
uint32_t* out_num_rb_frames, zx::vmo* out_buffer) {
if (req.notifications_per_ring > req.min_ring_buffer_frames) {
zxlogf(ERROR, "req.notifications_per_ring too big");
return ZX_ERR_OUT_OF_RANGE;
}
if (req.min_ring_buffer_frames > max_buffer_frames_) {
zxlogf(ERROR, "req.min_ring_buffer_frames too big");
return ZX_ERR_OUT_OF_RANGE;
}
num_ring_buffer_frames_ = std::max(
min_buffer_frames_,
fbl::round_up<uint32_t, uint32_t>(req.min_ring_buffer_frames, modulo_buffer_frames_));
uint32_t ring_buffer_size =
fbl::round_up<size_t, size_t>(num_ring_buffer_frames_ * frame_size_, ZX_PAGE_SIZE);
if (ring_buffer_mapper_.start() != nullptr) {
ring_buffer_mapper_.Unmap();
}
zx_status_t status = ring_buffer_mapper_.CreateAndMap(
ring_buffer_size, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr, &ring_buffer_vmo_,
ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP | ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER);
if (status != ZX_OK) {
zxlogf(ERROR, "%s failed to create ring buffer vmo - %d", __func__, status);
return status;
}
status = ring_buffer_vmo_.duplicate(
ZX_RIGHT_TRANSFER | ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP, out_buffer);
if (status != ZX_OK) {
zxlogf(ERROR, "%s failed to duplicate VMO handle for out param - %d", __func__, status);
return status;
}
notifications_per_ring_ = req.notifications_per_ring;
if (notifications_per_ring_ == 0) {
notification_period_ = zx::duration(0);
} else {
notification_period_ = zx::duration((zx::sec(1) * num_ring_buffer_frames_) /
(frame_rate_ * notifications_per_ring_));
}
if (using_alt_notifications_) {
if (alt_notifications_per_ring_ == 0) {
alt_notification_period_ = zx::duration(0);
} else {
alt_notification_period_ = zx::duration((zx::sec(1) * num_ring_buffer_frames_) /
(frame_rate_ * alt_notifications_per_ring_));
}
}
*out_num_rb_frames = num_ring_buffer_frames_;
zx::vmo duplicate_vmo;
status = ring_buffer_vmo_.duplicate(
ZX_RIGHT_TRANSFER | ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP, &duplicate_vmo);
if (status != ZX_OK) {
zxlogf(ERROR, "%s failed to duplicate VMO handle for notification - %d", __func__, status);
return status;
}
parent_->NotifyBufferCreated(std::move(duplicate_vmo), num_ring_buffer_frames_,
notifications_per_ring_);
return ZX_OK;
}
zx_status_t VirtualAudioStream::ChangeFormat(const audio::audio_proto::StreamSetFmtReq& req) {
// frame_size_ is already set, automatically
ZX_DEBUG_ASSERT(frame_size_);
frame_rate_ = req.frames_per_second;
ZX_DEBUG_ASSERT(frame_rate_);
sample_format_ = req.sample_format;
num_channels_ = req.channels;
bytes_per_sec_ = frame_rate_ * frame_size_;
// (Re)set external_delay_nsec_ and fifo_depth_ before leaving, if needed.
parent_->NotifySetFormat(frame_rate_, sample_format_, num_channels_, external_delay_nsec_);
return ZX_OK;
}
zx_status_t VirtualAudioStream::SetGain(const audio::audio_proto::SetGainReq& req) {
if (req.flags & AUDIO_SGF_GAIN_VALID) {
cur_gain_state_.cur_gain =
trunc(req.gain / cur_gain_state_.gain_step) * cur_gain_state_.gain_step;
}
if (req.flags & AUDIO_SGF_MUTE_VALID) {
cur_gain_state_.cur_mute = req.flags & AUDIO_SGF_MUTE;
}
if (req.flags & AUDIO_SGF_AGC_VALID) {
cur_gain_state_.cur_agc = req.flags & AUDIO_SGF_AGC;
}
parent_->NotifySetGain(cur_gain_state_.cur_mute, cur_gain_state_.cur_agc,
cur_gain_state_.cur_gain);
return ZX_OK;
}
// Drivers *must* report the time at which the first frame will be clocked out on the
// CLOCK_MONOTONIC timeline, not including any external delay.
zx_status_t VirtualAudioStream::Start(uint64_t* out_start_time) {
// Incorporate delay caused by fifo_depth_
start_time_ =
zx::clock::get_monotonic() + zx::duration((zx::sec(1) * fifo_depth_) / bytes_per_sec_);
// Set the timer here (if notifications are enabled).
if (notification_period_.get() > 0) {
notify_timer_.PostForTime(dispatcher(), start_time_);
target_notification_time_ = start_time_;
}
if (using_alt_notifications_ && alt_notification_period_.get() > 0) {
alt_notify_timer_.PostForTime(dispatcher(), start_time_);
target_alt_notification_time_ = start_time_;
}
parent_->NotifyStart(start_time_.get());
*out_start_time = start_time_.get();
return ZX_OK;
}
// Timer handler for sending out position notifications: to AudioCore, to VAD clients that do not
// override the notification frequency, and to VAD clients that set it to the same value that
// AudioCore has selected.
void VirtualAudioStream::ProcessRingNotification() {
audio::ScopedToken t(
domain_token()); // This method handles posts to notify_timer_ sent to dispatcher().
ZX_DEBUG_ASSERT(notification_period_.get() > 0);
auto monotonic_time = target_notification_time_.get();
ZX_DEBUG_ASSERT(monotonic_time > 0);
// TODO(MTWN-57): use a proper Timeline object here.
auto running_duration = target_notification_time_ - start_time_;
uint64_t frames = (running_duration * frame_rate_) / zx::sec(1);
uint32_t ring_buffer_position = (frames % num_ring_buffer_frames_) * frame_size_;
audio::audio_proto::RingBufPositionNotify resp = {};
resp.hdr.cmd = AUDIO_RB_POSITION_NOTIFY;
resp.monotonic_time = monotonic_time;
resp.ring_buffer_pos = ring_buffer_position;
NotifyPosition(resp);
if (!using_alt_notifications_) {
parent_->NotifyPosition(monotonic_time, ring_buffer_position);
}
target_notification_time_ += notification_period_;
notify_timer_.PostForTime(dispatcher(), target_notification_time_);
}
// Handler for sending alternate position notifications: those going to VAD clients that specified a
// different notification frequency. These are not sent to AudioCore.
void VirtualAudioStream::ProcessAltRingNotification() {
audio::ScopedToken t(
domain_token()); // This method handles posts to alt_notify_timer_ sent to dispatcher().
ZX_DEBUG_ASSERT(using_alt_notifications_);
ZX_DEBUG_ASSERT(alt_notification_period_.get() > 0);
auto monotonic_time = target_alt_notification_time_.get();
ZX_DEBUG_ASSERT(monotonic_time > 0);
// TODO(MTWN-57): use a proper Timeline object here.
auto running_duration = target_alt_notification_time_ - start_time_;
uint64_t frames = (running_duration * frame_rate_) / zx::sec(1);
uint32_t ring_buffer_position = (frames % num_ring_buffer_frames_) * frame_size_;
parent_->NotifyPosition(monotonic_time, ring_buffer_position);
target_alt_notification_time_ += alt_notification_period_;
alt_notify_timer_.PostForTime(dispatcher(), target_alt_notification_time_);
}
zx_status_t VirtualAudioStream::Stop() {
auto stop_time = zx::clock::get_monotonic();
notify_timer_.Cancel();
alt_notify_timer_.Cancel();
zx::duration running_duration = stop_time - start_time_;
uint64_t frames = (running_duration * frame_rate_) / zx::sec(1);
uint32_t ring_buf_position = (frames % num_ring_buffer_frames_) * frame_size_;
parent_->NotifyStop(stop_time.get(), ring_buf_position);
start_time_ = zx::time(0);
target_notification_time_ = zx::time(0);
target_alt_notification_time_ = zx::time(0);
notification_period_ = zx::duration(0);
alt_notification_period_ = zx::duration(0);
return ZX_OK;
}
// Called by parent SimpleAudioStream::Shutdown, during DdkUnbind. If our parent is not shutting
// down, then someone else called our DdkUnbind (perhaps the DevHost is removing our driver), and we
// should let our parent know so that it does not later try to Unbind us. Knowing who started the
// unwinding allows this to proceed in an orderly way, in all cases.
void VirtualAudioStream::ShutdownHook() {
if (!shutdown_by_parent_) {
parent_->ClearStream();
}
}
} // namespace virtual_audio