| // 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/affine/transform.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" |
| // #include "src/media/audio/lib/clock/utils.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::time VirtualAudioStream::MonoTimeFromRefTime(const zx::clock& clock, zx::time ref_time) { |
| zx_clock_details_v1_t clock_details; |
| zx_status_t status = clock.get_details(&clock_details); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| |
| zx_time_t mono_time = affine::Transform::ApplyInverse( |
| clock_details.mono_to_synthetic.reference_offset, |
| clock_details.mono_to_synthetic.synthetic_offset, |
| affine::Ratio(clock_details.mono_to_synthetic.rate.synthetic_ticks, |
| clock_details.mono_to_synthetic.rate.reference_ticks), |
| ref_time.get()); |
| |
| return zx::time{mono_time}; |
| } |
| |
| 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_; |
| clock_rate_adjustment_ = parent_->clock_rate_adjustment_; |
| zx_status_t status = EstablishReferenceClock(); |
| if (status != ZX_OK) { |
| zxlogf(WARNING, "EstablishReferenceClock failed: %d", status); |
| return status; |
| } |
| |
| 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); |
| |
| if (parent_->override_notification_frequency_) { |
| va_client_notifications_per_ring_ = parent_->notifications_per_ring_; |
| } |
| |
| return status; |
| } |
| |
| // We use this clock to emulate a real hardware time source. It is not exposed outside the driver. |
| zx_status_t VirtualAudioStream::EstablishReferenceClock() { |
| zx_status_t status = |
| zx::clock::create(ZX_CLOCK_OPT_MONOTONIC | ZX_CLOCK_OPT_CONTINUOUS | ZX_CLOCK_OPT_AUTO_START, |
| nullptr, &reference_clock_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Could not create driver clock"); |
| } else if (clock_rate_adjustment_ != 0) { |
| status = AdjustClockRate(); |
| } |
| return status; |
| } |
| |
| // Update the internal clock object that manages our variance from the local system timebase. |
| zx_status_t VirtualAudioStream::AdjustClockRate() { |
| zx::clock::update_args args; |
| args.reset().set_rate_adjust(clock_rate_adjustment_); |
| |
| zx_status_t status = reference_clock_.update(args); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Could not rate-adjust driver clock"); |
| ZX_ASSERT(status == ZX_OK); |
| } |
| |
| return status; |
| } |
| |
| // "UPDATE" actions to enqueue |
| 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::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::EnqueueClockRateAdjustment(int32_t ppm_from_monotonic) { |
| { |
| fbl::AutoLock lock(&wakeup_queue_lock_); |
| clock_rate_queue_.push_back(ppm_from_monotonic); |
| } |
| |
| set_clock_rate_wakeup_.Post(dispatcher()); |
| } |
| |
| // "GET" actions to enqueue |
| 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()); |
| } |
| |
| // Handle "UPDATE" actions |
| // This method handles tasks posted to plug_change_wakeup_ |
| void VirtualAudioStream::HandlePlugChanges() { |
| audio::ScopedToken t(domain_token()); |
| 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; |
| } |
| |
| 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. |
| } |
| } |
| } |
| |
| // This method receives tasks posted to set_notifications_wakeup_ |
| void VirtualAudioStream::HandleSetNotifications() { |
| audio::ScopedToken t(domain_token()); |
| |
| while (true) { |
| if (fbl::AutoLock lock(&wakeup_queue_lock_); !notifs_queue_.empty()) { |
| va_client_notifications_per_ring_ = notifs_queue_.front(); |
| notifs_queue_.pop_front(); |
| |
| // If our client requested the same notification cadence that AudioCore did, then just use the |
| // "official" AudioCore notification timer and frequency instead of this alternate mechanism. |
| if (va_client_notifications_per_ring_.value() == notifications_per_ring_) { |
| va_client_notifications_per_ring_ = std::nullopt; |
| } |
| SetVaClientNotificationPeriods(); |
| } else { |
| break; |
| } |
| } |
| |
| if (va_client_notifications_per_ring_.has_value() && |
| (va_client_notifications_per_ring_.value() > 0)) { |
| auto status = reference_clock_.read(target_va_client_ref_notification_time_.get_address()); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| PostForVaClientNotifyAt(target_va_client_ref_notification_time_); |
| } else { |
| target_va_client_mono_notification_time_ = zx::time(0); |
| va_client_notify_timer_.Cancel(); |
| } |
| } |
| |
| // This method receives tasks posted to set_clock_rate_wakeup_ |
| // Upon a rate adjustment, we cancel timers, adjust the clock, then re-set the timers |
| void VirtualAudioStream::HandleClockRateAdjustments() { |
| audio::ScopedToken t(domain_token()); |
| while (true) { |
| if (fbl::AutoLock lock(&wakeup_queue_lock_); !clock_rate_queue_.empty()) { |
| clock_rate_adjustment_ = clock_rate_queue_.front(); |
| clock_rate_queue_.pop_front(); |
| } else { |
| break; |
| } |
| } |
| |
| if (AdjustClockRate() != ZX_OK) { |
| zxlogf(ERROR, "AdjustClockRate failed in %s; continuing with existing clock", __func__); |
| } |
| } |
| |
| // Handle "GET" actions |
| // This method handles tasks posted to gain_request_wakeup_ |
| void VirtualAudioStream::HandleGainRequests() { |
| audio::ScopedToken t(domain_token()); |
| 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); |
| }); |
| } |
| } |
| |
| // This method handles tasks posted to format_request_wakeup_ |
| void VirtualAudioStream::HandleFormatRequests() { |
| audio::ScopedToken t(domain_token()); |
| 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(WARNING, "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); |
| }); |
| } |
| } |
| |
| // This method handles tasks posted to buffer_request_wakeup_ |
| void VirtualAudioStream::HandleBufferRequests() { |
| audio::ScopedToken t(domain_token()); |
| 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(WARNING, "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); |
| }); |
| } |
| } |
| |
| // This method handles tasks posted to position_request_wakeup_ |
| void VirtualAudioStream::HandlePositionRequests() { |
| audio::ScopedToken t(domain_token()); |
| 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 = ref_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(WARNING, "Stream is not started -- should not be calling GetPosition"); |
| return; |
| } |
| |
| zx::time ref_now; |
| auto status = reference_clock_.read(ref_now.get_address()); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| auto mono_now = MonoTimeFromRefTime(reference_clock_, ref_now); |
| |
| auto frames = ref_time_to_running_frame_.Apply(ref_now.get()); |
| uint32_t ring_buffer_position = (frames % num_rb_frames) * frame_size; |
| |
| parent_->PostToDispatcher( |
| [position_callback = std::move(position_callback), time_for_position = mono_now.get(), |
| ring_buffer_position]() { position_callback(time_for_position, ring_buffer_position); }); |
| } |
| } |
| |
| void VirtualAudioStream::PostForNotify() { |
| ZX_ASSERT(notifications_per_ring_); |
| ZX_ASSERT(target_mono_notification_time_.get() > 0); |
| |
| notify_timer_.PostForTime(dispatcher(), target_mono_notification_time_); |
| } |
| |
| void VirtualAudioStream::PostForNotifyAt(zx::time ref_notification_time) { |
| target_ref_notification_time_ = ref_notification_time; |
| target_mono_notification_time_ = |
| MonoTimeFromRefTime(reference_clock_, target_ref_notification_time_); |
| PostForNotify(); |
| } |
| |
| void VirtualAudioStream::PostForVaClientNotify() { |
| ZX_ASSERT(va_client_notifications_per_ring_.value() > 0); |
| ZX_ASSERT(target_va_client_mono_notification_time_.get() > 0); |
| |
| va_client_notify_timer_.PostForTime(dispatcher(), target_va_client_mono_notification_time_); |
| } |
| |
| void VirtualAudioStream::PostForVaClientNotifyAt(zx::time va_client_ref_notification_time) { |
| target_va_client_ref_notification_time_ = va_client_ref_notification_time; |
| target_va_client_mono_notification_time_ = |
| MonoTimeFromRefTime(reference_clock_, target_va_client_ref_notification_time_); |
| PostForVaClientNotify(); |
| } |
| |
| // On success, driver should return a valid VMO with appropriate permissions (READ | MAP | TRANSFER |
| // for input, plus WRITE for output) and report 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; |
| SetNotificationPeriods(); |
| |
| *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 VA client - %d", __func__, status); |
| return status; |
| } |
| parent_->NotifyBufferCreated(std::move(duplicate_vmo), num_ring_buffer_frames_, |
| notifications_per_ring_); |
| |
| return ZX_OK; |
| } |
| |
| void VirtualAudioStream::SetNotificationPeriods() { |
| if (notifications_per_ring_ > 0) { |
| ref_notification_period_ = zx::duration((zx::sec(1) * num_ring_buffer_frames_) / |
| (frame_rate_ * notifications_per_ring_)); |
| } else { |
| ref_notification_period_ = zx::duration(0); |
| } |
| |
| SetVaClientNotificationPeriods(); |
| } |
| |
| void VirtualAudioStream::SetVaClientNotificationPeriods() { |
| if (va_client_notifications_per_ring_.has_value() && |
| va_client_notifications_per_ring_.value() > 0) { |
| va_client_ref_notification_period_ = |
| zx::duration((zx::sec(1) * num_ring_buffer_frames_) / |
| (frame_rate_ * va_client_notifications_per_ring_.value())); |
| } else { |
| va_client_ref_notification_period_ = zx::duration(0); |
| } |
| } |
| |
| 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 (on CLOCK_MONOTONIC timeline) at which the first frame will be |
| // clocked out, not including any external delay. |
| zx_status_t VirtualAudioStream::Start(uint64_t* out_start_time) { |
| auto status = reference_clock_.read(ref_start_time_.get_address()); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| |
| // Incorporate delay caused by fifo_depth_ |
| ref_start_time_ += zx::duration((zx::sec(1) * fifo_depth_) / bytes_per_sec_); |
| |
| ref_time_to_running_frame_ = |
| affine::Transform(ref_start_time_.get(), 0, affine::Ratio(frame_rate_, ZX_SEC(1))); |
| |
| auto mono_start_time = MonoTimeFromRefTime(reference_clock_, ref_start_time_); |
| |
| parent_->NotifyStart(mono_start_time.get()); |
| |
| // Set the timer here (if notifications are enabled). |
| if (ref_notification_period_.get() > 0) { |
| PostForNotifyAt(ref_start_time_); |
| } |
| |
| if (va_client_ref_notification_period_.get() > 0) { |
| PostForVaClientNotifyAt(ref_start_time_); |
| } |
| |
| *out_start_time = mono_start_time.get(); |
| |
| return ZX_OK; |
| } |
| |
| // Timer handler for sending position notifications: to AudioCore, to VAD clients that don't set the |
| // notification frequency, and to VAD clients that set it to the same value that AudioCore selects. |
| // This method handles tasks posted to notify_timer_ |
| void VirtualAudioStream::ProcessRingNotification() { |
| audio::ScopedToken t(domain_token()); |
| ZX_ASSERT(ref_notification_period_.get() > 0); |
| ZX_ASSERT(notifications_per_ring_ > 0); |
| ZX_ASSERT(target_mono_notification_time_.get() > 0); |
| |
| zx::time ref_now; |
| auto status = reference_clock_.read(ref_now.get_address()); |
| ZX_ASSERT(status == ZX_OK); |
| |
| // We should wake up close to target_ref_notification_time_ |
| if (target_ref_notification_time_ > ref_now) { |
| PostForNotify(); // Too soon, re-post this for later |
| return; |
| } |
| |
| auto running_frame_position = |
| ref_time_to_running_frame_.Apply(target_ref_notification_time_.get()); |
| auto ring_buffer_position = (running_frame_position % num_ring_buffer_frames_) * frame_size_; |
| audio::audio_proto::RingBufPositionNotify resp = {}; |
| resp.hdr.cmd = AUDIO_RB_POSITION_NOTIFY; |
| resp.monotonic_time = target_mono_notification_time_.get(); |
| resp.ring_buffer_pos = ring_buffer_position; |
| |
| status = NotifyPosition(resp); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| if (status != ZX_OK) { |
| notifications_per_ring_ = 0; |
| return; |
| } |
| |
| // If virtual_audio client uses this notification cadence, notify them too |
| if (!va_client_notifications_per_ring_.has_value()) { |
| parent_->NotifyPosition(target_mono_notification_time_.get(), ring_buffer_position); |
| } |
| |
| // Post the next position notification |
| PostForNotifyAt(target_ref_notification_time_ + ref_notification_period_); |
| } |
| |
| // Handler for sending alternate position notifications: those going to VAD clients that specified |
| // a different notification frequency. These are not sent to AudioCore. |
| // This method receives tasks that have been posted to va_client_notify_timer_ |
| void VirtualAudioStream::ProcessVaClientRingNotification() { |
| audio::ScopedToken t(domain_token()); |
| ZX_DEBUG_ASSERT(va_client_ref_notification_period_.get() > 0); |
| ZX_DEBUG_ASSERT(va_client_notifications_per_ring_.has_value()); |
| ZX_DEBUG_ASSERT(va_client_notifications_per_ring_.value() > 0); |
| ZX_DEBUG_ASSERT(target_va_client_mono_notification_time_.get() > 0); |
| |
| zx::time ref_now; |
| auto status = reference_clock_.read(ref_now.get_address()); |
| ZX_ASSERT(status == ZX_OK); |
| |
| // We should wake up close to target_ref_notification_time_ |
| if (target_va_client_ref_notification_time_ > ref_now) { |
| PostForVaClientNotify(); // Too soon, re-post this for later |
| return; |
| } |
| |
| auto running_frame_position = |
| ref_time_to_running_frame_.Apply(target_va_client_ref_notification_time_.get()); |
| auto ring_buffer_position = (running_frame_position % num_ring_buffer_frames_) * frame_size_; |
| parent_->NotifyPosition(target_va_client_mono_notification_time_.get(), ring_buffer_position); |
| |
| // Post the next alt position notification |
| PostForVaClientNotifyAt(target_va_client_ref_notification_time_ + |
| va_client_ref_notification_period_); |
| } |
| |
| zx_status_t VirtualAudioStream::Stop() { |
| zx::time ref_stop_time; |
| auto status = reference_clock_.read(ref_stop_time.get_address()); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| auto mono_stop_time = MonoTimeFromRefTime(reference_clock_, ref_stop_time); |
| |
| notify_timer_.Cancel(); |
| va_client_notify_timer_.Cancel(); |
| |
| auto stop_frame = ref_time_to_running_frame_.Apply(ref_stop_time.get()); |
| uint32_t ring_buf_position = (stop_frame % num_ring_buffer_frames_) * frame_size_; |
| parent_->NotifyStop(mono_stop_time.get(), ring_buf_position); |
| |
| ref_start_time_ = zx::time(0); |
| target_mono_notification_time_ = zx::time(0); |
| target_va_client_mono_notification_time_ = zx::time(0); |
| ref_notification_period_ = zx::duration(0); |
| va_client_ref_notification_period_ = zx::duration(0); |
| |
| ref_time_to_running_frame_ = affine::Transform(affine::Ratio(0, 1)); |
| 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 |