| // Copyright 2022 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/services/device_registry/device.h" |
| |
| #include <fidl/fuchsia.audio.device/cpp/fidl.h> |
| #include <fidl/fuchsia.audio/cpp/common_types.h> |
| #include <fidl/fuchsia.audio/cpp/natural_types.h> |
| #include <fidl/fuchsia.hardware.audio.signalprocessing/cpp/markers.h> |
| #include <fidl/fuchsia.hardware.audio/cpp/fidl.h> |
| #include <lib/fidl/cpp/client.h> |
| #include <lib/fidl/cpp/wire/channel.h> |
| #include <lib/fit/function.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/clock.h> |
| #include <lib/zx/result.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| |
| #include "src/media/audio/lib/clock/real_clock.h" |
| #include "src/media/audio/lib/timeline/timeline_rate.h" |
| #include "src/media/audio/services/device_registry/basic_types.h" |
| #include "src/media/audio/services/device_registry/common.h" |
| #include "src/media/audio/services/device_registry/device_presence_watcher.h" |
| #include "src/media/audio/services/device_registry/logging.h" |
| #include "src/media/audio/services/device_registry/observer_notify.h" |
| #include "src/media/audio/services/device_registry/signal_processing_utils.h" |
| #include "src/media/audio/services/device_registry/validate.h" |
| |
| namespace media_audio { |
| |
| TokenId NextTokenId() { |
| static TokenId token_id = 0; |
| return token_id++; |
| } |
| |
| // Invoked when a RingBuffer channel drops. Device state previously was Configured/Paused/Started. |
| template <> |
| void Device::EndpointFidlErrorHandler<fuchsia_hardware_audio::RingBuffer>::on_fidl_error( |
| fidl::UnbindInfo info) { |
| ADR_LOG_METHOD(kLogRingBufferFidlResponses || kLogObjectLifetimes) << "(RingBuffer)"; |
| if (device()->state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error; no device state to unwind"; |
| } else if (info.is_peer_closed() || info.is_user_initiated()) { |
| ADR_LOG_METHOD(kLogRingBufferFidlResponses) << name() << " disconnected: " << info; |
| device()->DeviceDroppedRingBuffer(element_id_); |
| } else { |
| ADR_WARN_METHOD() << name() << " disconnected: " << info; |
| device()->OnError(info.status()); |
| } |
| auto& ring_buffer = device()->ring_buffer_map_.find(element_id_)->second; |
| ring_buffer.ring_buffer_client.reset(); |
| device()->ring_buffer_map_.erase(element_id_); |
| } |
| |
| // Invoked when a SignalProcessing channel drops. This can occur during device initialization. |
| template <> |
| void Device::FidlErrorHandler<fuchsia_hardware_audio_signalprocessing::SignalProcessing>:: |
| on_fidl_error(fidl::UnbindInfo info) { |
| ADR_LOG_METHOD(kLogRingBufferFidlResponses || kLogObjectLifetimes) << "(SignalProcessing)"; |
| // If a device already encountered some other error, disconnects like this are not unexpected. |
| if (device_->state_ == State::Error) { |
| ADR_WARN_METHOD() << "(signalprocessing) device already has error; no state to unwind"; |
| return; |
| } |
| // Check whether this occurred as a normal part of checking for signalprocessing support. |
| if (info.is_peer_closed() && info.status() == ZX_ERR_NOT_SUPPORTED) { |
| ADR_LOG_METHOD(kLogSignalProcessingFidlResponses || kLogDeviceState) |
| << name_ << ": signalprocessing not supported: " << info; |
| device_->SetSignalProcessingSupported(false); |
| return; |
| } |
| // Otherwise, we might just be unwinding this device (device-removal, or service shut-down). |
| if (info.is_peer_closed() || info.is_user_initiated()) { |
| ADR_LOG_METHOD(kLogSignalProcessingFidlResponses || kLogDeviceState) |
| << name_ << "(signalprocessing) disconnected: " << info; |
| device_->SetSignalProcessingSupported(false); |
| return; |
| } |
| // If none of the above, this driver's FIDL connection is now in doubt. Mark it as in ERROR. |
| ADR_WARN_METHOD() << name_ << "(signalprocessing) disconnected: " << info; |
| device_->SetSignalProcessingSupported(false); |
| device_->OnError(info.status()); |
| |
| device_->sig_proc_client_.reset(); |
| } |
| |
| // Invoked when the underlying driver disconnects. |
| template <typename T> |
| void Device::FidlErrorHandler<T>::on_fidl_error(fidl::UnbindInfo info) { |
| if (!info.is_peer_closed() && !info.is_user_initiated() && !info.is_dispatcher_shutdown()) { |
| ADR_WARN_METHOD() << name_ << " disconnected: " << info; |
| device_->OnError(info.status()); |
| } else { |
| ADR_LOG_METHOD(kLogCodecFidlResponses || kLogCompositeFidlResponses || |
| kLogStreamConfigFidlResponses || kLogObjectLifetimes) |
| << name_ << " disconnected: " << info; |
| } |
| device_->OnRemoval(); |
| } |
| |
| // static |
| std::shared_ptr<Device> Device::Create(std::weak_ptr<DevicePresenceWatcher> presence_watcher, |
| async_dispatcher_t* dispatcher, std::string_view name, |
| fuchsia_audio_device::DeviceType device_type, |
| fuchsia_audio_device::DriverClient driver_client) { |
| ADR_LOG_STATIC(kLogObjectLifetimes); |
| |
| // The constructor is private, forcing clients to use Device::Create(). |
| class MakePublicCtor : public Device { |
| public: |
| MakePublicCtor(std::weak_ptr<DevicePresenceWatcher> presence_watcher, |
| async_dispatcher_t* dispatcher, std::string_view name, |
| fuchsia_audio_device::DeviceType device_type, |
| fuchsia_audio_device::DriverClient driver_client) |
| : Device(std::move(presence_watcher), dispatcher, name, device_type, |
| std::move(driver_client)) {} |
| }; |
| |
| return std::make_shared<MakePublicCtor>(presence_watcher, dispatcher, name, device_type, |
| std::move(driver_client)); |
| } |
| |
| // Device notifies presence_watcher when it is available (DeviceInitialized), unhealthy (Error) or |
| // removed. The dispatcher member is needed for Device to create client connections to protocols |
| // such as fuchsia.hardware.audio.signalprocessing.Reader or fuchsia.hardware.audio.RingBuffer. |
| Device::Device(std::weak_ptr<DevicePresenceWatcher> presence_watcher, |
| async_dispatcher_t* dispatcher, std::string_view name, |
| fuchsia_audio_device::DeviceType device_type, |
| fuchsia_audio_device::DriverClient driver_client) |
| : presence_watcher_(std::move(presence_watcher)), |
| dispatcher_(dispatcher), |
| name_(name.substr(0, name.find('\0'))), |
| device_type_(device_type), |
| driver_client_(std::move(driver_client)), |
| token_id_(NextTokenId()) { |
| ADR_LOG_METHOD(kLogObjectLifetimes); |
| ++count_; |
| LogObjectCounts(); |
| |
| switch (device_type_) { |
| case fuchsia_audio_device::DeviceType::kCodec: |
| codec_handler_ = {this, "Codec"}; |
| codec_client_ = {driver_client_.codec().take().value(), dispatcher, &codec_handler_}; |
| break; |
| case fuchsia_audio_device::DeviceType::kComposite: |
| composite_handler_ = {this, "Composite"}; |
| composite_client_ = {driver_client_.composite().take().value(), dispatcher, |
| &composite_handler_}; |
| break; |
| case fuchsia_audio_device::DeviceType::kInput: |
| case fuchsia_audio_device::DeviceType::kOutput: |
| stream_config_handler_ = {this, "StreamConfig"}; |
| stream_config_client_ = {driver_client_.stream_config().take().value(), dispatcher, |
| &stream_config_handler_}; |
| break; |
| case fuchsia_audio_device::DeviceType::kDai: |
| ADR_WARN_METHOD() << "Dai device type is not yet implemented"; |
| OnError(ZX_ERR_WRONG_TYPE); |
| return; |
| default: |
| ADR_WARN_METHOD() << "Unknown DeviceType" << device_type_; |
| // Set device to error state |
| OnError(ZX_ERR_WRONG_TYPE); |
| return; |
| } |
| |
| // Upon creation, automatically kick off initialization. This will complete asynchronously, |
| // notifying the presence_watcher at that time by calling DeviceIsReady(). |
| Initialize(); |
| } |
| |
| Device::~Device() { |
| ADR_LOG_METHOD(kLogObjectLifetimes); |
| --count_; |
| LogObjectCounts(); |
| } |
| |
| // Called during initialization, when we know definitively whether the driver supports the protocol. |
| // It might also be called later, if there is an error related to the signalprocessing protocol. |
| // If this is the first time this is called, then call `OnInitializationResponse` to unblock the |
| // signalprocessing-related aspect of the "wait for multiple responses" state machine. |
| void Device::SetSignalProcessingSupported(bool is_supported) { |
| ADR_LOG_METHOD(kLogSignalProcessingFidlResponses); |
| auto first_set_of_signalprocessing_support = !supports_signalprocessing_.has_value(); |
| |
| supports_signalprocessing_ = is_supported; |
| |
| // Only poke the initialization state machine the FIRST time this is called. |
| if (first_set_of_signalprocessing_support) { |
| OnInitializationResponse(); |
| } |
| } |
| |
| void Device::OnRemoval() { |
| ADR_LOG_METHOD(kLogDeviceState); |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error; no device state to unwind"; |
| --unhealthy_count_; |
| } else if (state_ != State::DeviceInitializing) { |
| --initialized_count_; |
| |
| DropControl(); // Probably unneeded (the Device is going away) but makes unwind "complete". |
| ADR_LOG_OBJECT(kLogNotifyMethods) << "ForEachObserver => DeviceIsRemoved()" << token_id_ << ")"; |
| ForEachObserver([](auto obs) { obs->DeviceIsRemoved(); }); // Our control is also an observer. |
| } |
| |
| ring_buffer_map_.clear(); |
| sig_proc_client_.reset(); |
| codec_client_.reset(); |
| stream_config_client_.reset(); |
| |
| LogDeviceRemoval(info()); |
| LogObjectCounts(); |
| |
| // Regardless of whether device was pending / operational / unhealthy, notify the state watcher. |
| if (std::shared_ptr<DevicePresenceWatcher> pw = presence_watcher_.lock(); pw) { |
| pw->DeviceIsRemoved(shared_from_this()); |
| } |
| } |
| |
| void Device::ForEachObserver(fit::function<void(std::shared_ptr<ObserverNotify>)> action) { |
| for (auto weak_obs = observers_.begin(); weak_obs < observers_.end(); ++weak_obs) { |
| if (auto observer = weak_obs->lock(); observer) { |
| action(observer); |
| } |
| } |
| } |
| |
| // Unwind any operational state or configuration the device might be in, and remove this device. |
| void Device::OnError(zx_status_t error) { |
| ADR_LOG_METHOD(kLogDeviceState); |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error; ignoring subsequent error (" << error << ")"; |
| return; |
| } |
| |
| FX_PLOGS(WARNING, error) << __func__; |
| |
| if (state_ != State::DeviceInitializing) { |
| --initialized_count_; |
| |
| DropControl(); |
| ADR_LOG_OBJECT(kLogNotifyMethods) << "ForEachObserver => DeviceHasError"; |
| ForEachObserver([](auto obs) { obs->DeviceHasError(); }); |
| } |
| ++unhealthy_count_; |
| SetError(error); |
| LogDeviceError(info()); |
| LogObjectCounts(); |
| |
| if (std::shared_ptr<DevicePresenceWatcher> pw = presence_watcher_.lock(); pw) { |
| pw->DeviceHasError(shared_from_this()); |
| } |
| } |
| bool Device::IsFullyInitialized() { |
| switch (device_type_) { |
| case fuchsia_audio_device::DeviceType::kCodec: |
| return has_codec_properties() && has_health_state() && checked_for_signalprocessing() && |
| dai_format_sets_retrieved() && has_plug_state(); |
| case fuchsia_audio_device::DeviceType::kComposite: |
| return has_composite_properties() && has_health_state() && checked_for_signalprocessing() && |
| dai_format_sets_retrieved() && ring_buffer_format_sets_retrieved(); |
| case fuchsia_audio_device::DeviceType::kInput: |
| case fuchsia_audio_device::DeviceType::kOutput: |
| return has_stream_config_properties() && has_health_state() && |
| checked_for_signalprocessing() && ring_buffer_format_sets_retrieved() && |
| has_gain_state() && has_plug_state(); |
| case fuchsia_audio_device::DeviceType::kDai: |
| ADR_WARN_METHOD() << "Don't yet support Dai"; |
| return false; |
| default: |
| ADR_WARN_METHOD() << "Invalid device_type_"; |
| return false; |
| } |
| } |
| |
| // An initialization command returned a successful response. Is initialization complete? |
| void Device::OnInitializationResponse() { |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device has already encountered a problem; ignoring this"; |
| return; |
| } |
| |
| if (state_ != State::DeviceInitializing) { |
| ADR_WARN_METHOD() << "unexpected device initialization response when not Initializing"; |
| } |
| |
| switch (device_type_) { |
| case fuchsia_audio_device::DeviceType::kCodec: |
| ADR_LOG_METHOD(kLogDeviceInitializationProgress) |
| << " (RECEIVED|pending)" // |
| << " " << (has_codec_properties() ? "PROPS" : "props") // |
| << " " << (has_health_state() ? "HEALTH" : "health") // |
| << " " << (checked_for_signalprocessing() ? "SIGPROC" : "sigproc") // |
| << " " << (dai_format_sets_retrieved() ? "DAIFORMATS" : "daiformats") // |
| << " " << (has_plug_state() ? "PLUG" : "plug"); |
| break; |
| case fuchsia_audio_device::DeviceType::kComposite: |
| ADR_LOG_METHOD(kLogDeviceInitializationProgress) |
| << " (RECEIVED|pending)" // |
| << " " << (has_composite_properties() ? "PROPS" : "props") // |
| << " " << (has_health_state() ? "HEALTH" : "health") // |
| << " " << (checked_for_signalprocessing() ? "SIGPROC" : "sigproc") // |
| << " " << (dai_format_sets_retrieved() ? "DAIFORMATS" : "daiformats") // |
| << " " << (ring_buffer_format_sets_retrieved() ? "RB_FORMATS" : "rb_formats"); |
| break; |
| case fuchsia_audio_device::DeviceType::kInput: |
| case fuchsia_audio_device::DeviceType::kOutput: |
| ADR_LOG_METHOD(kLogDeviceInitializationProgress) |
| << " (RECEIVED|pending)" // |
| << " " << (has_stream_config_properties() ? "PROPS" : "props") // |
| << " " << (has_health_state() ? "HEALTH" : "health") // |
| << " " << (checked_for_signalprocessing() ? "SIGPROC" : "sigproc") // |
| << " " << (ring_buffer_format_sets_retrieved() ? "RB_FORMATS" : "rb_formats") // |
| << " " << (has_plug_state() ? "PLUG" : "plug") // |
| << " " << (has_gain_state() ? "GAIN" : "gain"); |
| break; |
| case fuchsia_audio_device::DeviceType::kDai: |
| default: |
| ADR_WARN_METHOD() << "Invalid device_type_"; |
| return; |
| } |
| |
| if (IsFullyInitialized()) { |
| ++initialized_count_; |
| SetDeviceInfo(); |
| LogDeviceAddition(*info()); |
| SetState(State::DeviceInitialized); |
| LogObjectCounts(); |
| |
| if (std::shared_ptr<DevicePresenceWatcher> pw = presence_watcher_.lock(); pw) { |
| pw->DeviceIsReady(shared_from_this()); |
| } |
| } |
| } |
| |
| bool Device::SetControl(std::shared_ptr<ControlNotify> control_notify) { |
| ADR_LOG_METHOD(kLogDeviceState || kLogNotifyMethods); |
| FX_CHECK(state_ != State::DeviceInitializing); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device has an error; cannot set control"; |
| return false; |
| } |
| |
| if (GetControlNotify()) { |
| ADR_WARN_METHOD() << "already controlled"; |
| return false; |
| } |
| |
| if (state_ != State::DeviceInitialized) { |
| ADR_WARN_METHOD() << "wrong state for this call (" << state_ << ")"; |
| return false; |
| } |
| |
| control_notify_ = control_notify; |
| AddObserver(std::move(control_notify)); |
| |
| // For this new control, "catch it up" on the current state. |
| if (auto notify = GetControlNotify(); notify) { |
| // DaiFormat(s) |
| if (is_codec()) { |
| if (codec_format_) { |
| notify->DaiFormatChanged(fuchsia_audio_device::kDefaultDaiInterconnectElementId, |
| codec_format_->dai_format, codec_format_->codec_format_info); |
| } else { |
| notify->DaiFormatChanged(fuchsia_audio_device::kDefaultDaiInterconnectElementId, |
| std::nullopt, std::nullopt); |
| } |
| // Codec start state |
| codec_start_state_.started ? notify->CodecStarted(codec_start_state_.start_stop_time) |
| : notify->CodecStopped(codec_start_state_.start_stop_time); |
| } else if (is_composite()) { |
| for (auto [element_id, dai_format] : composite_dai_formats_) { |
| notify->DaiFormatChanged(element_id, dai_format, std::nullopt); |
| } |
| } |
| } |
| |
| LogObjectCounts(); |
| return true; |
| } |
| |
| bool Device::DropControl() { |
| ADR_LOG_METHOD(kLogDeviceMethods || kLogNotifyMethods); |
| FX_CHECK(state_ != State::DeviceInitializing); |
| |
| auto control_notify = GetControlNotify(); |
| if (!control_notify) { |
| ADR_LOG_METHOD(kLogNotifyMethods) << "already not controlled"; |
| return false; |
| } |
| |
| // Need to drop ring buffers here? |
| |
| control_notify_.reset(); |
| // We don't remove our ControlNotify from the observer list: we wait for it to self-invalidate. |
| |
| return true; |
| } |
| |
| bool Device::AddObserver(const std::shared_ptr<ObserverNotify>& observer_to_add) { |
| ADR_LOG_METHOD(kLogDeviceMethods || kLogNotifyMethods) << " (" << observer_to_add << ")"; |
| |
| FX_CHECK(state_ != State::DeviceInitializing); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << ": unhealthy, cannot be observed"; |
| return false; |
| } |
| for (auto weak_obs = observers_.begin(); weak_obs < observers_.end(); ++weak_obs) { |
| if (auto observer = weak_obs->lock(); observer) { |
| if (observer == observer_to_add) { |
| // Because observers are not explicitly dropped (they are weakly held and allowed to |
| // self-invalidate), this can occur if a Control is dropped then immediately readded. |
| // This is OK because the outcome is ultimately correct (it is still in the vector). |
| FX_LOGS(WARNING) << "Device(" << this << ")::AddObserver: observer cannot be re-added"; |
| return false; |
| } |
| } |
| } |
| observers_.push_back(observer_to_add); |
| |
| // For this new observer, "catch it up" on any current state that we already know. |
| // This may include: |
| // |
| // (1) current signalprocessing topology. |
| if (current_topology_id_.has_value()) { |
| observer_to_add->TopologyChanged(*current_topology_id_); |
| } |
| |
| // (2) ElementState, for each signalprocessing element where this has already been retrieved. |
| for (const auto& element_record : sig_proc_element_map_) { |
| if (element_record.second.state.has_value()) { |
| observer_to_add->ElementStateChanged(element_record.first, *element_record.second.state); |
| } |
| } |
| |
| // (3) GainState (if StreamConfig). |
| if (is_stream_config()) { |
| observer_to_add->GainStateChanged({{ |
| .gain_db = *gain_state_->gain_db(), |
| .muted = gain_state_->muted().value_or(false), |
| .agc_enabled = gain_state_->agc_enabled().value_or(false), |
| }}); |
| } |
| |
| // (4) PlugState (if Codec or StreamConfig). |
| if (is_codec() || is_stream_config()) { |
| observer_to_add->PlugStateChanged(*plug_state_->plugged() |
| ? fuchsia_audio_device::PlugState::kPlugged |
| : fuchsia_audio_device::PlugState::kUnplugged, |
| zx::time(*plug_state_->plug_state_time())); |
| } |
| |
| LogObjectCounts(); |
| |
| // SetState(State::DeviceInitialized); |
| return true; |
| } |
| |
| // The Device dropped the driver RingBuffer FIDL. Notify any clients. |
| void Device::DeviceDroppedRingBuffer(ElementId element_id) { |
| ADR_LOG_METHOD(kLogDeviceState); |
| |
| // This is distinct from DropRingBuffer in case we must notify our RingBuffer (via our Control). |
| // We do so if we have 1) a Control and 2) a driver_format_ (thus a client-configured RingBuffer). |
| if (auto notify = GetControlNotify(); notify) { |
| if (auto rb_pair = ring_buffer_map_.find(element_id); |
| rb_pair != ring_buffer_map_.end() && rb_pair->second.driver_format.has_value()) |
| notify->DeviceDroppedRingBuffer(element_id); |
| } |
| DropRingBuffer(element_id); |
| } |
| |
| // Whether client- or device-originated, reset any state associated with an active RingBuffer. |
| void Device::DropRingBuffer(ElementId element_id) { |
| ADR_LOG_METHOD(kLogDeviceState); |
| |
| // if (state_ != State::Error) { |
| // SetState(State::DeviceInitialized); |
| // } |
| |
| auto rb_pair = ring_buffer_map_.find(element_id); |
| if (rb_pair == ring_buffer_map_.end()) { |
| return; |
| } |
| |
| // If we've already cleaned out any state with the underlying driver RingBuffer, then we're done. |
| auto& rb_record = rb_pair->second; |
| if (!rb_record.ring_buffer_client.has_value() || !rb_record.ring_buffer_client->is_valid()) { |
| return; |
| } |
| |
| // Revert all configuration state related to the ring buffer. |
| // |
| rb_record.start_time.reset(); // Pause, if we are started. |
| |
| rb_record.requested_ring_buffer_bytes |
| .reset(); // User must call CreateRingBuffer again, leading to ... |
| rb_record.create_ring_buffer_callback = nullptr; |
| |
| rb_record.driver_format.reset(); // ... our re-calling ConnectToRingBufferFidl ... |
| rb_record.vmo_format = {}; |
| rb_record.num_ring_buffer_frames.reset(); // ... and GetVmo ... |
| rb_record.ring_buffer_vmo.reset(); |
| |
| rb_record.ring_buffer_properties.reset(); // ... and GetProperties ... |
| |
| rb_record.delay_info.reset(); // ... and WatchDelayInfo ... |
| rb_record.bytes_per_frame = 0; // (.. which calls CalculateRequiredRingBufferSizes ...) |
| rb_record.requested_ring_buffer_frames = 0u; |
| rb_record.ring_buffer_producer_bytes = 0; |
| rb_record.ring_buffer_consumer_bytes = 0; |
| |
| rb_record.active_channels_bitmask = 0; // ... and SetActiveChannels. |
| rb_record.active_channels_set_time.reset(); |
| |
| // Clear our FIDL connection to the driver RingBuffer. |
| (void)rb_record.ring_buffer_client->UnbindMaybeGetEndpoint(); |
| rb_record.ring_buffer_client.reset(); |
| } |
| |
| void Device::SetError(zx_status_t error) { |
| ADR_LOG_METHOD(kLogDeviceState) << ": " << error; |
| state_ = State::Error; |
| } |
| |
| void Device::SetState(State state) { |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error; ignoring this"; |
| return; |
| } |
| |
| ADR_LOG_METHOD(kLogDeviceState) << state; |
| state_ = state; |
| } |
| |
| void Device::SetRingBufferState(ElementId element_id, RingBufferState ring_buffer_state) { |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error; ignoring this"; |
| return; |
| } |
| |
| auto rb_pair = ring_buffer_map_.find(element_id); |
| if (rb_pair == ring_buffer_map_.end()) { |
| ADR_WARN_METHOD() << "couldn't find RingBuffer for element_id " << element_id; |
| return; |
| } |
| |
| ADR_LOG_METHOD(kLogRingBufferState) << ring_buffer_state; |
| rb_pair->second.ring_buffer_state = ring_buffer_state; |
| } |
| |
| void Device::Initialize() { |
| ADR_LOG_METHOD(kLogDeviceMethods); |
| |
| if (is_codec()) { |
| RetrieveCodecProperties(); |
| RetrieveCodecHealthState(); |
| RetrieveSignalProcessingState(); |
| RetrieveCodecDaiFormatSets(); |
| RetrieveCodecPlugState(); |
| } else if (is_composite()) { |
| RetrieveCompositeProperties(); |
| RetrieveCompositeHealthState(); |
| RetrieveSignalProcessingState(); // On completion, this starts format-set-retrieval. |
| } else if (is_stream_config()) { |
| RetrieveStreamProperties(); |
| RetrieveStreamHealthState(); |
| RetrieveSignalProcessingState(); |
| RetrieveStreamRingBufferFormatSets(); |
| RetrieveStreamPlugState(); |
| RetrieveGainState(); |
| } else { |
| FX_LOGS(WARNING) << "Different device type: " << device_type_; |
| } |
| } |
| |
| // This method also sets OnError, so it is not just for logging. |
| // Use this when the Result might contain a domain_error or framework_error. |
| template <typename ResultT> |
| bool Device::LogResultError(const ResultT& result, const char* debug_context) { |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << debug_context << ": device already has an error"; |
| return true; |
| } |
| if (result.is_error()) { |
| if (result.error_value().is_framework_error()) { |
| if (result.error_value().framework_error().is_canceled() || |
| result.error_value().framework_error().is_peer_closed()) { |
| ADR_LOG_METHOD(kLogCodecFidlResponses || kLogCompositeFidlResponses || |
| kLogStreamConfigFidlResponses) |
| << debug_context << ": will take no action on " << result.error_value(); |
| } else { |
| FX_LOGS(ERROR) << debug_context << " failed: " << result.error_value() << ")"; |
| OnError(result.error_value().framework_error().status()); |
| } |
| } else { |
| FX_LOGS(ERROR) << debug_context << " failed: " << result.error_value() << ")"; |
| OnError(ZX_ERR_INTERNAL); |
| } |
| } |
| return result.is_error(); |
| } |
| |
| // This method also sets OnError, so it is not just for logging. |
| // Use this when the Result error can only be a framework_error. |
| template <typename ResultT> |
| bool Device::LogResultFrameworkError(const ResultT& result, const char* debug_context) { |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << debug_context << ": device already has an error"; |
| return true; |
| } |
| if (result.is_error()) { |
| if (result.error_value().is_canceled() || result.error_value().is_peer_closed()) { |
| ADR_LOG_METHOD(kLogCodecFidlResponses || kLogCompositeFidlResponses || |
| kLogStreamConfigFidlResponses) |
| << debug_context << ": will take no action on " << result.error_value(); |
| } else { |
| FX_LOGS(ERROR) << debug_context << " failed: " << result.error_value().status() << " (" |
| << result.error_value() << ")"; |
| OnError(result.error_value().status()); |
| } |
| } |
| return result.is_error(); |
| } |
| |
| void Device::RetrieveStreamProperties() { |
| ADR_LOG_METHOD(kLogStreamConfigFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| // TODO(https://fxbug.dev/42064765): handle command timeouts |
| |
| (*stream_config_client_) |
| ->GetProperties() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::StreamConfig::GetProperties>& result) { |
| if (LogResultFrameworkError(result, "GetProperties response")) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogStreamConfigFidlResponses) << "StreamConfig/GetProperties: success"; |
| if (!ValidateStreamProperties(result->properties())) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| FX_CHECK(!has_stream_config_properties()) |
| << "StreamConfig/GetProperties response: stream_config_properties_ already set"; |
| stream_config_properties_ = result->properties(); |
| SanitizeStreamPropertiesStrings(stream_config_properties_); |
| // We have our clock domain now. Create the device clock. |
| CreateDeviceClock(); |
| |
| OnInitializationResponse(); |
| }); |
| } |
| |
| // Some drivers return manufacturer or product strings with embedded '\0' characters. Trim them. |
| void Device::SanitizeStreamPropertiesStrings( |
| std::optional<fuchsia_hardware_audio::StreamProperties>& stream_properties) { |
| if (!stream_properties) { |
| FX_LOGS(ERROR) << __func__ << " called with unspecified StreamProperties"; |
| return; |
| } |
| |
| if (stream_properties->manufacturer()) { |
| stream_properties->manufacturer(stream_properties->manufacturer()->substr( |
| 0, std::min<uint64_t>(stream_properties->manufacturer()->find('\0'), |
| fuchsia_hardware_audio::kMaxUiStringSize - 1))); |
| } |
| if (stream_properties->product()) { |
| stream_properties->product(stream_properties->product()->substr( |
| 0, std::min<uint64_t>(stream_properties->product()->find('\0'), |
| fuchsia_hardware_audio::kMaxUiStringSize - 1))); |
| } |
| } |
| |
| void Device::RetrieveCodecProperties() { |
| ADR_LOG_METHOD(kLogCodecFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| // TODO(https://fxbug.dev/113429): handle command timeouts |
| |
| (*codec_client_) |
| ->GetProperties() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::Codec::GetProperties>& result) { |
| if (LogResultFrameworkError(result, "GetProperties response")) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogCodecFidlResponses) << "Codec/GetProperties: success"; |
| if (!ValidateCodecProperties(result->properties())) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| FX_CHECK(!has_codec_properties()) |
| << "Codec/GetProperties response: codec_properties_ already set"; |
| codec_properties_ = result->properties(); |
| SanitizeCodecPropertiesStrings(codec_properties_); |
| |
| OnInitializationResponse(); |
| }); |
| } |
| |
| void Device::SanitizeCodecPropertiesStrings( |
| std::optional<fuchsia_hardware_audio::CodecProperties>& codec_properties) { |
| if (!codec_properties) { |
| FX_LOGS(ERROR) << __func__ << " called with unspecified CodecProperties"; |
| return; |
| } |
| |
| if (codec_properties->manufacturer()) { |
| codec_properties->manufacturer(codec_properties->manufacturer()->substr( |
| 0, std::min<uint64_t>(codec_properties->manufacturer()->find('\0'), |
| fuchsia_hardware_audio::kMaxUiStringSize - 1))); |
| } |
| if (codec_properties->product()) { |
| codec_properties->product(codec_properties->product()->substr( |
| 0, std::min<uint64_t>(codec_properties->product()->find('\0'), |
| fuchsia_hardware_audio::kMaxUiStringSize - 1))); |
| } |
| } |
| |
| void Device::RetrieveCompositeProperties() { |
| ADR_LOG_METHOD(kLogCompositeFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error; ignoring this"; |
| return; |
| } |
| // TODO(https://fxbug.dev/113429): handle command timeouts |
| |
| (*composite_client_) |
| ->GetProperties() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::Composite::GetProperties>& result) { |
| if (LogResultFrameworkError(result, "GetProperties response")) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogCompositeFidlResponses) << "Composite/GetProperties: success"; |
| if (!ValidateCompositeProperties(result->properties())) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| FX_CHECK(!has_composite_properties()) |
| << "Composite/GetProperties response: composite_properties_ already set"; |
| composite_properties_ = result->properties(); |
| SanitizeCompositePropertiesStrings(composite_properties_); |
| // We have our clock domain now. Create the device clock. |
| CreateDeviceClock(); |
| |
| OnInitializationResponse(); |
| }); |
| } |
| |
| void Device::SanitizeCompositePropertiesStrings( |
| std::optional<fuchsia_hardware_audio::CompositeProperties>& composite_properties) { |
| if (!composite_properties) { |
| FX_LOGS(ERROR) << __func__ << " called with unspecified CompositeProperties"; |
| return; |
| } |
| |
| if (composite_properties->manufacturer()) { |
| composite_properties->manufacturer(composite_properties->manufacturer()->substr( |
| 0, std::min<uint64_t>(composite_properties->manufacturer()->find('\0'), |
| fuchsia_hardware_audio::kMaxUiStringSize - 1))); |
| } |
| if (composite_properties->product()) { |
| composite_properties->product(composite_properties->product()->substr( |
| 0, std::min<uint64_t>(composite_properties->product()->find('\0'), |
| fuchsia_hardware_audio::kMaxUiStringSize - 1))); |
| } |
| } |
| |
| void Device::RetrieveStreamRingBufferFormatSets() { |
| ADR_LOG_METHOD(kLogStreamConfigFidlCalls); |
| |
| RetrieveRingBufferFormatSets( |
| fuchsia_audio_device::kDefaultRingBufferElementId, |
| [this](ElementId element_id, |
| const std::vector<fuchsia_hardware_audio::SupportedFormats>& ring_buffer_format_sets) { |
| if (state_ == State::Error) { |
| ADR_WARN_OBJECT() << "device has an error while retrieving initial RingBuffer formats"; |
| return; |
| } |
| |
| // Required for StreamConfig and Dai, absent for Codec and Composite. |
| auto translated_ring_buffer_format_sets = |
| TranslateRingBufferFormatSets(ring_buffer_format_sets); |
| if (translated_ring_buffer_format_sets.empty()) { |
| ADR_WARN_OBJECT() << "RingBuffer format sets could not be translated"; |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| }; |
| element_driver_ring_buffer_format_sets_ = {{ |
| { |
| element_id, |
| ring_buffer_format_sets, |
| }, |
| }}; |
| element_ring_buffer_format_sets_ = {{ |
| fuchsia_audio_device::ElementRingBufferFormatSet{{ |
| .element_id = element_id, |
| .format_sets = translated_ring_buffer_format_sets, |
| }}, |
| }}; |
| ring_buffer_format_sets_retrieved_ = true; |
| OnInitializationResponse(); |
| }); |
| } |
| |
| void Device::RetrieveRingBufferFormatSets( |
| ElementId element_id, |
| fit::callback<void(ElementId, const std::vector<fuchsia_hardware_audio::SupportedFormats>&)> |
| ring_buffer_format_sets_callback) { |
| ADR_LOG_METHOD(kLogStreamConfigFidlCalls || kLogRingBufferMethods); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| // TODO(https://fxbug.dev/42064765): handle command timeouts |
| |
| if (element_id != fuchsia_audio_device::kDefaultRingBufferElementId) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| ring_buffer_endpoint_ids_.insert(fuchsia_audio_device::kDefaultRingBufferElementId); |
| |
| (*stream_config_client_) |
| ->GetSupportedFormats() |
| .Then([this, element_id, rb_formats_callback = std::move(ring_buffer_format_sets_callback)]( |
| fidl::Result<fuchsia_hardware_audio::StreamConfig::GetSupportedFormats>& |
| result) mutable { |
| if (LogResultFrameworkError(result, "GetSupportedFormats response")) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogStreamConfigFidlResponses) |
| << "StreamConfig/GetSupportedFormats: success"; |
| if (!ValidateRingBufferFormatSets(result->supported_formats())) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| rb_formats_callback(element_id, result->supported_formats()); |
| }); |
| } |
| |
| void Device::RetrieveSignalProcessingState() { |
| ADR_LOG_METHOD(kLogDeviceMethods); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| |
| auto [sig_proc_client_end, sig_proc_server_end] = |
| fidl::Endpoints<fuchsia_hardware_audio_signalprocessing::SignalProcessing>::Create(); |
| |
| // TODO(https://fxbug.dev/113429): handle command timeouts |
| |
| if (is_codec()) { |
| ADR_LOG_METHOD(kLogCodecFidlCalls) << "calling SignalProcessingConnect"; |
| if (!codec_client_->is_valid()) { |
| return; |
| } |
| auto status = (*codec_client_)->SignalProcessingConnect(std::move(sig_proc_server_end)); |
| |
| if (status.is_error()) { |
| if (status.error_value().is_canceled()) { |
| // These indicate that we are already shutting down, so they aren't error conditions. |
| ADR_LOG_METHOD(kLogCodecFidlResponses) |
| << "SignalProcessingConnect response will take no action on error " |
| << status.error_value().FormatDescription(); |
| return; |
| } |
| |
| FX_PLOGS(ERROR, status.error_value().status()) << __func__ << " returned error:"; |
| OnError(status.error_value().status()); |
| return; |
| } |
| } else if (is_composite()) { |
| ADR_LOG_METHOD(kLogCompositeFidlCalls) << "calling SignalProcessingConnect"; |
| auto status = (*composite_client_)->SignalProcessingConnect(std::move(sig_proc_server_end)); |
| |
| if (status.is_error()) { |
| if (status.error_value().is_canceled()) { |
| // These indicate that we are already shutting down, so they aren't error conditions. |
| ADR_LOG_METHOD(kLogCompositeFidlResponses) |
| << "SignalProcessingConnect response will take no action on error " |
| << status.error_value().FormatDescription(); |
| return; |
| } |
| |
| FX_PLOGS(ERROR, status.error_value().status()) << __func__ << " returned error:"; |
| OnError(status.error_value().status()); |
| return; |
| } |
| } else if (is_stream_config()) { |
| ADR_LOG_METHOD(kLogStreamConfigFidlCalls) << "calling SignalProcessingConnect"; |
| if (!stream_config_client_->is_valid()) { |
| return; |
| } |
| |
| auto status = (*stream_config_client_)->SignalProcessingConnect(std::move(sig_proc_server_end)); |
| |
| if (status.is_error()) { |
| if (status.error_value().is_canceled()) { |
| // These indicate that we are already shutting down, so they aren't error conditions. |
| ADR_LOG_METHOD(kLogStreamConfigFidlResponses) |
| << "SignalProcessingConnect response will take no action on error " |
| << status.error_value().FormatDescription(); |
| return; |
| } |
| |
| FX_PLOGS(ERROR, status.error_value().status()) << __func__ << " returned error:"; |
| OnError(status.error_value().status()); |
| return; |
| } |
| } |
| |
| sig_proc_client_.emplace(); |
| sig_proc_handler_ = {this, "SignalProcessing"}; |
| sig_proc_client_->Bind(std::move(sig_proc_client_end), dispatcher_, &sig_proc_handler_); |
| |
| RetrieveSignalProcessingElements(); |
| } |
| |
| void Device::RetrieveSignalProcessingElements() { |
| ADR_LOG_METHOD(kLogSignalProcessingFidlCalls); |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| |
| (*sig_proc_client_) |
| ->GetElements() |
| .Then( |
| [this]( |
| fidl::Result<fuchsia_hardware_audio_signalprocessing::SignalProcessing::GetElements>& |
| result) { |
| std::string context("signalprocessing::GetElements response"); |
| if (result.is_error() && result.error_value().is_domain_error() && |
| result.error_value().domain_error() == ZX_ERR_NOT_SUPPORTED) { |
| ADR_LOG_OBJECT(kLogSignalProcessingFidlResponses) << context << ": NOT_SUPPORTED"; |
| |
| SetSignalProcessingSupported(false); |
| return; |
| } |
| if (LogResultError(result, context.c_str())) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogSignalProcessingFidlResponses) << context; |
| if (!ValidateElements(result->processing_elements())) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| sig_proc_elements_ = result->processing_elements(); |
| sig_proc_element_map_ = MapElements(sig_proc_elements_); |
| if (sig_proc_element_map_.empty()) { |
| ADR_WARN_OBJECT() << "Empty element map"; |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| for (const auto& [element_id, _] : sig_proc_element_map_) { |
| element_ids_.insert(element_id); |
| } |
| RetrieveSignalProcessingTopologies(); |
| |
| if (is_composite()) { |
| RetrieveCompositeDaiFormatSets(); |
| RetrieveCompositeRingBufferFormatSets(); |
| } |
| }); |
| } |
| |
| void Device::RetrieveSignalProcessingTopologies() { |
| ADR_LOG_METHOD(kLogSignalProcessingFidlCalls); |
| if (state_ == State::Error || (checked_for_signalprocessing() && !supports_signalprocessing())) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| FX_DCHECK(sig_proc_client_->is_valid()); |
| |
| (*sig_proc_client_) |
| ->GetTopologies() |
| .Then([this](fidl::Result< |
| fuchsia_hardware_audio_signalprocessing::SignalProcessing::GetTopologies>& |
| result) { |
| std::string context("signalprocessing::GetTopologies response"); |
| if (result.is_error() && result.error_value().is_domain_error() && |
| result.error_value().domain_error() == ZX_ERR_NOT_SUPPORTED) { |
| SetSignalProcessingSupported(false); |
| return; |
| } |
| if (LogResultError(result, context.c_str())) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogSignalProcessingFidlResponses) << context; |
| if (!ValidateTopologies(result->topologies(), sig_proc_element_map_)) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| sig_proc_topologies_ = result->topologies(); |
| sig_proc_topology_map_ = MapTopologies(sig_proc_topologies_); |
| if (sig_proc_topology_map_.empty()) { |
| ADR_WARN_OBJECT() << "Empty topology map"; |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| for (const auto& [topology_id, _] : sig_proc_topology_map_) { |
| topology_ids_.insert(topology_id); |
| } |
| |
| SetSignalProcessingSupported(true); |
| |
| // Now we know we support signalprocessing: query the current topology/element states. |
| RetrieveCurrentTopology(); |
| RetrieveCurrentElementStates(); |
| }); |
| } |
| |
| void Device::RetrieveCurrentTopology() { |
| ADR_LOG_METHOD(kLogSignalProcessingFidlCalls); |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| FX_DCHECK(sig_proc_client_->is_valid()); |
| |
| (*sig_proc_client_) |
| ->WatchTopology() |
| .Then([this](fidl::Result< |
| fuchsia_hardware_audio_signalprocessing::SignalProcessing::WatchTopology>& |
| result) { |
| TopologyId topology_id = result->topology_id(); |
| std::string context("signalprocessing::WatchTopology response: topology_id "); |
| context.append(std::to_string(topology_id)); |
| if (LogResultFrameworkError(result, context.c_str())) { |
| return; |
| } |
| ADR_LOG_OBJECT(kLogSignalProcessingFidlResponses) << context; |
| |
| // Either (a) sig_proc_topology_map_ is incorrect, or (b) the driver is poorly-behaved. |
| if (sig_proc_topology_map_.find(topology_id) == sig_proc_topology_map_.end()) { |
| FX_LOGS(ERROR) << context << ": not found"; |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| // Save the topology and notify Observers, but only if this is a change in topology. |
| if (!current_topology_id_.has_value() || *current_topology_id_ != topology_id) { |
| current_topology_id_ = topology_id; |
| ADR_LOG_OBJECT(kLogNotifyMethods) |
| << "ForEachObserver => TopologyChanged: " << topology_id; |
| ForEachObserver([topology_id](auto obs) { obs->TopologyChanged(topology_id); }); |
| } |
| |
| // Register for any future change in topology, whether this was a change or not. |
| RetrieveCurrentTopology(); |
| }); |
| } |
| |
| void Device::RetrieveCurrentElementStates() { |
| ADR_LOG_METHOD(kLogSignalProcessingFidlCalls); |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| FX_DCHECK(sig_proc_client_->is_valid()); |
| |
| for (auto& [element_id, _] : sig_proc_element_map_) { |
| RetrieveElementState(element_id); |
| } |
| } |
| |
| void Device::RetrieveElementState(ElementId element_id) { |
| auto element_match = sig_proc_element_map_.find(element_id); |
| FX_CHECK(element_match != sig_proc_element_map_.end()); |
| auto& element = element_match->second.element; |
| |
| (*sig_proc_client_) |
| ->WatchElementState({element_id}) |
| .Then([this, element_id, |
| element](fidl::Result< |
| fuchsia_hardware_audio_signalprocessing::SignalProcessing::WatchElementState>& |
| result) { |
| std::string context("signalprocessing::WatchElementState response: element_id "); |
| context.append(std::to_string(element_id)); |
| if (LogResultFrameworkError(result, context.c_str())) { |
| return; |
| } |
| ADR_LOG_OBJECT(kLogSignalProcessingFidlResponses) << context; |
| |
| // Either (a) sig_proc_topology_map_.at(element_id).element is incorrect, or (b) the driver |
| // is behaving incorrectly. |
| auto element_state = result->state(); |
| if (!ValidateElementState(element_state, element)) { |
| FX_LOGS(ERROR) << context << ": not found"; |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| // Save the state and notify Observers, but only if this is a change in element state. |
| auto element_record = sig_proc_element_map_.find(*element.id()); |
| if (element_record->second.state.has_value() && |
| *element_record->second.state == element_state) { |
| ADR_LOG_OBJECT(kLogNotifyMethods) |
| << "Not sending ElementStateChanged: state is unchanged."; |
| } else { |
| element_record->second.state = element_state; |
| // Notify any Observers of this change in element state. |
| ADR_LOG_OBJECT(kLogNotifyMethods) |
| << "ForEachObserver => ElementStateChanged(" << element_id << ")"; |
| ForEachObserver([element_id, element_state](auto obs) { |
| obs->ElementStateChanged(element_id, element_state); |
| }); |
| } |
| |
| // Register for any future change in element state, whether this was a change or not. |
| RetrieveElementState(element_id); |
| }); |
| } |
| |
| // If the method does not return ZX_OK, then the driver was not called. |
| zx_status_t Device::SetTopology(uint64_t topology_id) { |
| ADR_LOG_METHOD(kLogSignalProcessingFidlCalls); |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return ZX_ERR_IO; |
| } |
| |
| if (!supports_signalprocessing()) { |
| ADR_WARN_METHOD() << "device does not support signalprocessing"; |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| if (!GetControlNotify()) { |
| ADR_WARN_METHOD() << "Device must be controlled before this method can be called"; |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| FX_DCHECK(sig_proc_client_->is_valid()); |
| if (sig_proc_topology_map_.find(topology_id) == sig_proc_topology_map_.end()) { |
| ADR_WARN_METHOD() << "invalid topology_id " << topology_id; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // We don't check/prevent "no-change" here, since current_topology_id_ may not reflect in-flight |
| // updates. We update current_topology_id_ and call ObserverNotify::TopologyChanged (or |
| // not, if no change) at only one place: the WatchTopology response handler. |
| |
| (*sig_proc_client_) |
| ->SetTopology(topology_id) |
| .Then( |
| [this, topology_id]( |
| fidl::Result<fuchsia_hardware_audio_signalprocessing::SignalProcessing::SetTopology>& |
| result) { |
| std::string context("SigProc::SetTopology response: topology_id "); |
| context.append(std::to_string(topology_id)); |
| if (LogResultError(result, context.c_str())) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogSignalProcessingFidlResponses) << context; |
| // Our hanging WatchTopology call will complete now, updating topology_id_ and calling |
| // ObserverNotify::TopologyChanged (or not, if no change). |
| }); |
| |
| return ZX_OK; |
| } |
| |
| // If the method does not return ZX_OK, then the driver was not called. |
| zx_status_t Device::SetElementState( |
| ElementId element_id, |
| const fuchsia_hardware_audio_signalprocessing::ElementState& element_state) { |
| ADR_LOG_METHOD(kLogSignalProcessingFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return ZX_ERR_IO; |
| } |
| if (!supports_signalprocessing()) { |
| ADR_WARN_METHOD() << "device does not support signalprocessing"; |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| if (!GetControlNotify()) { |
| ADR_WARN_METHOD() << "Device must be controlled before this method can be called"; |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| FX_DCHECK(sig_proc_client_->is_valid()); |
| if (sig_proc_element_map_.find(element_id) == sig_proc_element_map_.end()) { |
| ADR_WARN_METHOD() << "invalid element_id " << element_id; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (!ValidateElementState(element_state, sig_proc_element_map_.at(element_id).element)) { |
| ADR_WARN_METHOD() << "invalid ElementState for element_id " << element_id; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // We don't check/prevent "no-change" here, since sig_proc_element_map_ may not reflect in-flight |
| // updates. We update sig_proc_element_map_ and call ObserverNotify::ElementStateChanged (or |
| // not, if no change) at only one place: the WatchElementState response handler. |
| |
| (*sig_proc_client_) |
| ->SetElementState({element_id, element_state}) |
| .Then([this, element_id]( |
| fidl::Result< |
| fuchsia_hardware_audio_signalprocessing::SignalProcessing::SetElementState>& |
| result) { |
| std::string context("SigProc::SetElementState response: element_id "); |
| context.append(std::to_string(element_id)); |
| if (LogResultError(result, context.c_str())) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogSignalProcessingFidlResponses) << context; |
| // Our hanging WatchElementState call will complete now, updating our cached state and |
| // calling ObserverNotify::ElementStateChanged (or not, if no change). |
| }); |
| |
| return ZX_OK; |
| } |
| |
| void Device::RetrieveCodecDaiFormatSets() { |
| ADR_LOG_METHOD(kLogCodecFidlCalls); |
| RetrieveDaiFormatSets( |
| fuchsia_audio_device::kDefaultDaiInterconnectElementId, |
| [this](ElementId element_id, |
| const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& dai_format_sets) { |
| if (state_ == State::Error) { |
| ADR_WARN_OBJECT() << "device already has an error"; |
| return; |
| } |
| element_dai_format_sets_.push_back({{element_id, dai_format_sets}}); |
| dai_format_sets_retrieved_ = true; |
| OnInitializationResponse(); |
| }); |
| } |
| |
| // We have our signalprocessing Elements now; GetDaiFormats on each DAI Endpoint. |
| void Device::RetrieveCompositeDaiFormatSets() { |
| ADR_LOG_METHOD(kLogCompositeFidlCalls); |
| dai_endpoint_ids_ = dai_endpoints(sig_proc_element_map_); |
| temp_dai_endpoint_ids_ = dai_endpoint_ids_; |
| element_dai_format_sets_.clear(); |
| for (auto element_id : temp_dai_endpoint_ids_) { |
| RetrieveDaiFormatSets( |
| element_id, |
| [this](ElementId element_id, |
| const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& dai_format_sets) { |
| element_dai_format_sets_.push_back({{element_id, dai_format_sets}}); |
| temp_dai_endpoint_ids_.erase(element_id); |
| if (temp_dai_endpoint_ids_.empty()) { |
| dai_format_sets_retrieved_ = true; |
| OnInitializationResponse(); |
| } |
| }); |
| } |
| } |
| |
| void Device::RetrieveDaiFormatSets( |
| ElementId element_id, |
| fit::callback<void(ElementId, const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>&)> |
| dai_format_sets_callback) { |
| ADR_LOG_METHOD(kLogCodecFidlCalls || kLogCompositeFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| // TODO(https://fxbug.dev/113429): handle command timeouts |
| |
| if (is_codec()) { |
| if (element_id != fuchsia_audio_device::kDefaultDaiInterconnectElementId) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| (*codec_client_) |
| ->GetDaiFormats() |
| .Then([this, element_id, callback = std::move(dai_format_sets_callback)]( |
| fidl::Result<fuchsia_hardware_audio::Codec::GetDaiFormats>& result) mutable { |
| if (LogResultError(result, "GetDaiFormats response")) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogCodecFidlResponses) << "Codec/GetDaiFormats: success"; |
| |
| if (!ValidateDaiFormatSets(result->formats())) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| callback(element_id, result->formats()); |
| }); |
| } else if (is_composite()) { |
| ADR_LOG_METHOD(kLogCompositeFidlCalls) << " GetDaiFormats (element " << element_id << ")"; |
| (*composite_client_) |
| ->GetDaiFormats(element_id) |
| .Then([this, element_id, callback = std::move(dai_format_sets_callback)]( |
| fidl::Result<fuchsia_hardware_audio::Composite::GetDaiFormats>& result) mutable { |
| std::string str{"GetDaiFormats (element "}; |
| str.append(std::to_string(element_id)).append(") response"); |
| ADR_LOG_OBJECT(kLogCompositeFidlResponses) << str; |
| if (state_ == State::Error) { |
| ADR_WARN_OBJECT() << "device already has error during " << str; |
| return; |
| } |
| if (LogResultError(result, str.c_str())) { |
| return; |
| } |
| if (!ValidateDaiFormatSets(result->dai_formats())) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| callback(element_id, result->dai_formats()); |
| }); |
| } |
| } |
| |
| // We have our signalprocessing Elements now; GetDaiFormats on each RingBuffer Endpoint. |
| void Device::RetrieveCompositeRingBufferFormatSets() { |
| ADR_LOG_METHOD(kLogCompositeFidlCalls); |
| ring_buffer_endpoint_ids_ = ring_buffer_endpoints(sig_proc_element_map_); |
| temp_ring_buffer_endpoint_ids_ = ring_buffer_endpoint_ids_; |
| |
| element_ring_buffer_format_sets_.clear(); |
| for (auto id : ring_buffer_endpoint_ids_) { |
| ADR_LOG_METHOD(kLogCompositeFidlCalls) << " GetRingBufferFormats (element " << id << ")"; |
| (*composite_client_) |
| ->GetRingBufferFormats(id) |
| .Then([this, |
| id](fidl::Result<fuchsia_hardware_audio::Composite::GetRingBufferFormats>& result) { |
| std::string str{"GetRingBufferFormats (element "}; |
| str.append(std::to_string(id)).append(") response"); |
| ADR_LOG_OBJECT(kLogCompositeFidlResponses) << str; |
| if (state_ == State::Error) { |
| ADR_WARN_OBJECT() << "device already has error during " << str; |
| return; |
| } |
| if (LogResultError(result, str.c_str())) { |
| return; |
| } |
| if (!ValidateRingBufferFormatSets(result->ring_buffer_formats())) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| auto translated_ring_buffer_format_sets = |
| TranslateRingBufferFormatSets(result->ring_buffer_formats()); |
| if (translated_ring_buffer_format_sets.empty()) { |
| ADR_WARN_OBJECT() << "Failed to translate " << str; |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| element_driver_ring_buffer_format_sets_.emplace_back(id, result->ring_buffer_formats()); |
| element_ring_buffer_format_sets_.push_back({{id, translated_ring_buffer_format_sets}}); |
| temp_ring_buffer_endpoint_ids_.erase(id); |
| if (temp_ring_buffer_endpoint_ids_.empty()) { |
| ring_buffer_format_sets_retrieved_ = true; |
| OnInitializationResponse(); |
| } |
| }); |
| } |
| } |
| |
| void Device::RetrieveGainState() { |
| ADR_LOG_METHOD(kLogStreamConfigFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| if (!has_gain_state()) { |
| // TODO(https://fxbug.dev/42064765): handle command timeout on initial (not subsequent) watches. |
| } |
| |
| (*stream_config_client_) |
| ->WatchGainState() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::StreamConfig::WatchGainState>& result) { |
| if (LogResultFrameworkError(result, "GainState response")) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogStreamConfigFidlResponses) << "WatchGainState response"; |
| if (!ValidateGainState(result->gain_state())) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| auto old_gain_state = gain_state_; |
| gain_state_ = result->gain_state(); |
| gain_state_->muted() = gain_state_->muted().value_or(false); |
| gain_state_->agc_enabled() = gain_state_->agc_enabled().value_or(false); |
| |
| if (!old_gain_state) { |
| ADR_LOG_OBJECT(kLogStreamConfigFidlResponses) << "WatchGainState received initial value"; |
| OnInitializationResponse(); |
| } else { |
| ADR_LOG_OBJECT(kLogStreamConfigFidlResponses) << "WatchGainState received update"; |
| ADR_LOG_OBJECT(kLogNotifyMethods) << "ForEachObserver => GainStateChanged"; |
| ForEachObserver([gain_state = *gain_state_](auto obs) { |
| obs->GainStateChanged({{ |
| .gain_db = *gain_state.gain_db(), |
| .muted = gain_state.muted().value_or(false), |
| .agc_enabled = gain_state.agc_enabled().value_or(false), |
| }}); |
| }); |
| } |
| // Kick off the next watch. |
| RetrieveGainState(); |
| }); |
| } |
| |
| void Device::RetrieveStreamPlugState() { |
| ADR_LOG_METHOD(kLogStreamConfigFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| if (!has_plug_state()) { |
| // TODO(https://fxbug.dev/42064765): handle command timeouts (but not on subsequent watches) |
| } |
| |
| (*stream_config_client_) |
| ->WatchPlugState() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::StreamConfig::WatchPlugState>& result) { |
| if (LogResultFrameworkError(result, "PlugState response")) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogStreamConfigFidlResponses) << "WatchPlugState response"; |
| std::optional<fuchsia_hardware_audio::PlugDetectCapabilities> plug_detect_capabilities; |
| if (has_stream_config_properties()) { |
| plug_detect_capabilities = stream_config_properties_->plug_detect_capabilities(); |
| } |
| if (!ValidatePlugState(result->plug_state(), plug_detect_capabilities)) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| auto old_plug_state = plug_state_; |
| plug_state_ = result->plug_state(); |
| |
| if (!old_plug_state) { |
| ADR_LOG_OBJECT(kLogStreamConfigFidlResponses) << "WatchPlugState received initial value"; |
| OnInitializationResponse(); |
| } else { |
| ADR_LOG_OBJECT(kLogStreamConfigFidlResponses) << "WatchPlugState received update"; |
| ADR_LOG_OBJECT(kLogNotifyMethods) << "ForEachObserver => PlugStateChanged"; |
| ForEachObserver([plug_state = *plug_state_](auto obs) { |
| obs->PlugStateChanged(plug_state.plugged().value_or(true) |
| ? fuchsia_audio_device::PlugState::kPlugged |
| : fuchsia_audio_device::PlugState::kUnplugged, |
| zx::time(*plug_state.plug_state_time())); |
| }); |
| } |
| // Kick off the next watch. |
| RetrieveStreamPlugState(); |
| }); |
| } |
| |
| void Device::RetrieveCodecPlugState() { |
| ADR_LOG_METHOD(kLogCodecFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| if (!has_plug_state()) { |
| // TODO(https://fxbug.dev/113429): handle command timeouts (but not on subsequent watches) |
| } |
| |
| (*codec_client_) |
| ->WatchPlugState() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::Codec::WatchPlugState>& result) { |
| if (LogResultFrameworkError(result, "Codec::PlugState response")) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogCodecFidlResponses) << "Codec::WatchPlugState response"; |
| std::optional<fuchsia_hardware_audio::PlugDetectCapabilities> plug_detect_capabilities; |
| if (has_codec_properties()) { |
| plug_detect_capabilities = codec_properties_->plug_detect_capabilities(); |
| } |
| if (!ValidatePlugState(result->plug_state(), plug_detect_capabilities)) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| auto old_plug_state = plug_state_; |
| plug_state_ = result->plug_state(); |
| |
| if (!old_plug_state) { |
| ADR_LOG_OBJECT(kLogCodecFidlResponses) << "Codec::WatchPlugState received initial value"; |
| OnInitializationResponse(); |
| } else { |
| ADR_LOG_OBJECT(kLogCodecFidlResponses) << "Codec::WatchPlugState received update"; |
| ADR_LOG_OBJECT(kLogNotifyMethods) << "ForEachObserver => PlugStateChanged"; |
| ForEachObserver([plug_state = *plug_state_](auto obs) { |
| obs->PlugStateChanged(plug_state.plugged().value_or(true) |
| ? fuchsia_audio_device::PlugState::kPlugged |
| : fuchsia_audio_device::PlugState::kUnplugged, |
| zx::time(*plug_state.plug_state_time())); |
| }); |
| } |
| // Kick off the next watch. |
| RetrieveCodecPlugState(); |
| }); |
| } |
| |
| // TODO(https://fxbug.dev/42068381): Decide when we proactively call GetHealthState, if at all. |
| void Device::RetrieveStreamHealthState() { |
| ADR_LOG_METHOD(kLogStreamConfigFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| // TODO(https://fxbug.dev/42064765): handle command timeouts |
| |
| (*stream_config_client_) |
| ->GetHealthState() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::StreamConfig::GetHealthState>& result) { |
| if (LogResultFrameworkError(result, "HealthState response")) { |
| return; |
| } |
| |
| auto old_health_state = health_state_; |
| |
| // An empty health state is permitted; it still indicates that the driver is responsive. |
| health_state_ = result->state().healthy().value_or(true); |
| // ...but if the driver actually self-reported as unhealthy, this is a problem. |
| if (!*health_state_) { |
| FX_LOGS(WARNING) |
| << "RetrieveStreamConfigHealthState response: .healthy is FALSE (unhealthy)"; |
| OnError(ZX_ERR_IO); |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogStreamConfigFidlResponses) |
| << "RetrieveStreamConfigHealthState response: healthy"; |
| if (!old_health_state) { |
| OnInitializationResponse(); |
| } |
| }); |
| } |
| |
| void Device::RetrieveCodecHealthState() { |
| ADR_LOG_METHOD(kLogCodecFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| // TODO(https://fxbug.dev/113429): handle command timeouts, because that's the most likely |
| // indicator of an unhealthy driver/device. |
| |
| (*codec_client_) |
| ->GetHealthState() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::Codec::GetHealthState>& result) { |
| if (LogResultFrameworkError(result, "HealthState response")) { |
| return; |
| } |
| |
| auto old_health_state = health_state_; |
| |
| // An empty health state is permitted; it still indicates that the driver is responsive. |
| health_state_ = result->state().healthy().value_or(true); |
| // ...but if the driver actually self-reported as unhealthy, this is a problem. |
| if (!*health_state_) { |
| FX_LOGS(WARNING) << "RetrieveCodecHealthState response: .healthy is FALSE (unhealthy)"; |
| OnError(ZX_ERR_IO); |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogCodecFidlResponses) << "RetrieveCodecHealthState response: healthy"; |
| if (!old_health_state) { |
| OnInitializationResponse(); |
| } |
| }); |
| } |
| |
| void Device::RetrieveCompositeHealthState() { |
| ADR_LOG_METHOD(kLogCompositeFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error; ignoring this"; |
| return; |
| } |
| |
| // TODO(https://fxbug.dev/113429): handle command timeouts, because that's the most likely |
| // indicator of an unhealthy driver/device. |
| |
| (*composite_client_) |
| ->GetHealthState() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::Composite::GetHealthState>& result) { |
| if (LogResultFrameworkError(result, "HealthState response")) { |
| return; |
| } |
| |
| auto old_health_state = health_state_; |
| |
| // An empty health state is permitted; it still indicates that the driver is responsive. |
| health_state_ = result->state().healthy().value_or(true); |
| // ...but if the driver actually self-reported as unhealthy, this is a problem. |
| if (!*health_state_) { |
| FX_LOGS(WARNING) |
| << "RetrieveCompositeHealthState response: .healthy is FALSE (unhealthy)"; |
| OnError(ZX_ERR_IO); |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogCompositeFidlResponses) |
| << "RetrieveCompositeHealthState response: healthy"; |
| if (!old_health_state) { |
| OnInitializationResponse(); |
| } |
| }); |
| } |
| |
| // Return a fuchsia_audio_device::Info object based on this device's member values. |
| // Required fields (guaranteed for the caller) include: token_id, device_type, device_name. |
| // Other fields are required for some driver types but optional or absent for others. |
| fuchsia_audio_device::Info Device::CreateDeviceInfo() { |
| ADR_LOG_METHOD(kLogDeviceMethods); |
| |
| auto info = fuchsia_audio_device::Info{{ |
| // Required for all device types: |
| .token_id = token_id_, |
| .device_type = device_type_, |
| .device_name = name_, |
| }}; |
| // Required for Composite; optional for Codec, Dai and StreamConfig: |
| if (supports_signalprocessing()) { |
| info.signal_processing_elements(sig_proc_elements_); |
| info.signal_processing_topologies(sig_proc_topologies_); |
| } |
| if (is_codec()) { |
| // Optional for all device types. |
| info.manufacturer(codec_properties_->manufacturer()) |
| .product(codec_properties_->product()) |
| // Required for Dai and StreamConfig; optional for Codec: |
| .is_input(codec_properties_->is_input()) |
| // Required for Codec and Dai; optional for Composite; absent for StreamConfig: |
| .dai_format_sets(dai_format_sets()) |
| // Required for Codec and StreamConfig; absent for Composite and Dai: |
| .plug_detect_caps(*codec_properties_->plug_detect_capabilities() == |
| fuchsia_hardware_audio::PlugDetectCapabilities::kHardwired |
| ? fuchsia_audio_device::PlugDetectCapabilities::kHardwired |
| : fuchsia_audio_device::PlugDetectCapabilities::kPluggable); |
| // Codec properties stores unique_id as a string, so we must handle as a special case. |
| if (codec_properties_->unique_id()) { |
| std::array<unsigned char, fuchsia_audio_device::kUniqueInstanceIdSize> uid{}; |
| memcpy(uid.data(), codec_properties_->unique_id()->data(), |
| fuchsia_audio_device::kUniqueInstanceIdSize); |
| info.unique_instance_id(uid); |
| } |
| } else if (is_composite()) { |
| // Optional for all device types: |
| info.manufacturer(composite_properties_->manufacturer()) |
| .product(composite_properties_->product()) |
| .unique_instance_id(composite_properties_->unique_id()) |
| // Required for Dai and StreamConfig; optional for Composite; absent for Codec: |
| .ring_buffer_format_sets(element_ring_buffer_format_sets_) |
| // Required for Codec and Dai; optional for Composite; absent for StreamConfig: |
| .dai_format_sets(element_dai_format_sets_) |
| // Required for Composite, Dai and StreamConfig; absent for Codec: |
| .clock_domain(composite_properties_->clock_domain()); |
| } else if (is_stream_config()) { |
| // Optional for all device types: |
| info.manufacturer(stream_config_properties_->manufacturer()) |
| .product(stream_config_properties_->product()) |
| .unique_instance_id(stream_config_properties_->unique_id()) |
| // Required for Dai and StreamConfig; optional for Codec; absent for Composite: |
| .is_input(stream_config_properties_->is_input()) |
| // Required for Dai and StreamConfig; optional for Composite; absent for Codec: |
| .ring_buffer_format_sets(ring_buffer_format_sets()) |
| // Required for StreamConfig; absent for Codec, Composite and Dai: |
| .gain_caps(fuchsia_audio_device::GainCapabilities{{ |
| .min_gain_db = stream_config_properties_->min_gain_db(), |
| .max_gain_db = stream_config_properties_->max_gain_db(), |
| .gain_step_db = stream_config_properties_->gain_step_db(), |
| .can_mute = stream_config_properties_->can_mute(), |
| .can_agc = stream_config_properties_->can_agc(), |
| }}) |
| // Required for Codec and StreamConfig; absent for Composite and Dai: |
| .plug_detect_caps(*stream_config_properties_->plug_detect_capabilities() == |
| fuchsia_hardware_audio::PlugDetectCapabilities::kHardwired |
| ? fuchsia_audio_device::PlugDetectCapabilities::kHardwired |
| : fuchsia_audio_device::PlugDetectCapabilities::kPluggable) |
| // Required for Composite, Dai and StreamConfig; absent for Codec: |
| .clock_domain(stream_config_properties_->clock_domain()); |
| } |
| |
| return info; |
| } |
| |
| void Device::SetDeviceInfo() { |
| ADR_LOG_METHOD(kLogDeviceMethods); |
| |
| device_info_ = CreateDeviceInfo(); |
| |
| if (!sig_proc_element_map_.empty()) { |
| LogElementMap(sig_proc_element_map_); |
| } |
| |
| (void)ValidateDeviceInfo(*device_info_); |
| } |
| |
| void Device::CreateDeviceClock() { |
| ADR_LOG_METHOD(kLogDeviceMethods); |
| |
| ClockDomain clock_domain; |
| if (is_stream_config()) { |
| FX_CHECK(stream_config_properties_->clock_domain()) << "Clock domain is required"; |
| clock_domain = stream_config_properties_->clock_domain().value_or( |
| fuchsia_hardware_audio::kClockDomainMonotonic); |
| } else if (is_composite()) { |
| FX_CHECK(composite_properties_->clock_domain()) << "Clock domain is required"; |
| clock_domain = composite_properties_->clock_domain().value_or( |
| fuchsia_hardware_audio::kClockDomainMonotonic); |
| } else { |
| ADR_WARN_METHOD() << "Cannot create a device clock for device_type " << device_type(); |
| return; |
| } |
| |
| device_clock_ = RealClock::CreateFromMonotonic( |
| "'" + name_ + "' device clock", clock_domain, |
| (clock_domain != fuchsia_hardware_audio::kClockDomainMonotonic)); |
| } |
| |
| // Create a duplicate handle to our clock with limited rights. We can transfer it to a client who |
| // can only read and duplicate. Specifically, they cannot change this clock's rate or offset. |
| zx::result<zx::clock> Device::GetReadOnlyClock() const { |
| ADR_LOG_METHOD(kLogDeviceMethods); |
| |
| if (!device_clock_) { |
| // This device type does not expose a clock. |
| return zx::error(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| auto dupe_clock = device_clock_->DuplicateZxClockReadOnly(); |
| if (!dupe_clock) { |
| return zx::error(ZX_ERR_ACCESS_DENIED); |
| } |
| |
| return zx::ok(std::move(*dupe_clock)); |
| } |
| |
| // Determine the full fuchsia_hardware_audio::Format needed for ConnectRingBufferFidl. |
| // This method expects that the required fields are present. |
| std::optional<fuchsia_hardware_audio::Format> Device::SupportedDriverFormatForClientFormat( |
| ElementId element_id, const fuchsia_audio::Format& client_format) { |
| fuchsia_hardware_audio::SampleFormat driver_sample_format; |
| uint8_t bytes_per_sample, max_valid_bits; |
| auto client_sample_type = *client_format.sample_type(); |
| auto channel_count = *client_format.channel_count(); |
| auto frame_rate = *client_format.frames_per_second(); |
| |
| switch (client_sample_type) { |
| case fuchsia_audio::SampleType::kUint8: |
| driver_sample_format = fuchsia_hardware_audio::SampleFormat::kPcmUnsigned; |
| max_valid_bits = 8; |
| bytes_per_sample = 1; |
| break; |
| case fuchsia_audio::SampleType::kInt16: |
| driver_sample_format = fuchsia_hardware_audio::SampleFormat::kPcmSigned; |
| max_valid_bits = 16; |
| bytes_per_sample = 2; |
| break; |
| case fuchsia_audio::SampleType::kInt32: |
| driver_sample_format = fuchsia_hardware_audio::SampleFormat::kPcmSigned; |
| max_valid_bits = 32; |
| bytes_per_sample = 4; |
| break; |
| case fuchsia_audio::SampleType::kFloat32: |
| driver_sample_format = fuchsia_hardware_audio::SampleFormat::kPcmFloat; |
| max_valid_bits = 32; |
| bytes_per_sample = 4; |
| break; |
| case fuchsia_audio::SampleType::kFloat64: |
| driver_sample_format = fuchsia_hardware_audio::SampleFormat::kPcmFloat; |
| max_valid_bits = 64; |
| bytes_per_sample = 8; |
| break; |
| default: |
| FX_CHECK(false) << "Unhandled fuchsia_audio::SampleType: " |
| << static_cast<uint32_t>(client_sample_type); |
| } |
| |
| std::vector<fuchsia_hardware_audio::SupportedFormats> driver_ring_buffer_format_sets; |
| for (const auto& element_entry_pair : element_driver_ring_buffer_format_sets_) { |
| if (element_entry_pair.first == element_id) { |
| driver_ring_buffer_format_sets = element_entry_pair.second; |
| } |
| } |
| if (driver_ring_buffer_format_sets.empty()) { |
| FX_LOGS(WARNING) << __func__ << ": no driver ring_buffer_format_set found for element_id " |
| << element_id; |
| return {}; |
| } |
| // If format/bytes/rate/channels all match, save the highest valid_bits within our limit. |
| uint8_t best_valid_bits = 0; |
| for (const auto& ring_buffer_format_set : driver_ring_buffer_format_sets) { |
| const auto pcm_format_set = *ring_buffer_format_set.pcm_supported_formats(); |
| if (std::count_if( |
| pcm_format_set.sample_formats()->begin(), pcm_format_set.sample_formats()->end(), |
| [driver_sample_format](const auto& f) { return f == driver_sample_format; }) && |
| std::count_if(pcm_format_set.bytes_per_sample()->begin(), |
| pcm_format_set.bytes_per_sample()->end(), |
| [bytes_per_sample](const auto& bs) { return bs == bytes_per_sample; }) && |
| std::count_if(pcm_format_set.frame_rates()->begin(), pcm_format_set.frame_rates()->end(), |
| [frame_rate](const auto& fr) { return fr == frame_rate; }) && |
| std::count_if(pcm_format_set.channel_sets()->begin(), pcm_format_set.channel_sets()->end(), |
| [channel_count](const fuchsia_hardware_audio::ChannelSet& cs) { |
| return cs.attributes()->size() == channel_count; |
| })) { |
| std::for_each(pcm_format_set.valid_bits_per_sample()->begin(), |
| pcm_format_set.valid_bits_per_sample()->end(), |
| [max_valid_bits, &best_valid_bits](uint8_t v_bits) { |
| if (v_bits <= max_valid_bits) { |
| best_valid_bits = std::max(best_valid_bits, v_bits); |
| } |
| }); |
| } |
| } |
| |
| if (!best_valid_bits) { |
| FX_LOGS(WARNING) << __func__ << ": no intersection for client format: " |
| << static_cast<uint16_t>(channel_count) << "-chan " << frame_rate << "hz " |
| << client_sample_type; |
| return {}; |
| } |
| |
| ADR_LOG_METHOD(kLogRingBufferFidlResponseValues) |
| << "successful match for client format: " << channel_count << "-chan " << frame_rate << "hz " |
| << client_sample_type << " (valid_bits " << static_cast<uint16_t>(best_valid_bits) << ")"; |
| |
| return fuchsia_hardware_audio::Format{{ |
| fuchsia_hardware_audio::PcmFormat{{ |
| .number_of_channels = static_cast<uint8_t>(channel_count), |
| .sample_format = driver_sample_format, |
| .bytes_per_sample = bytes_per_sample, |
| .valid_bits_per_sample = best_valid_bits, |
| .frame_rate = frame_rate, |
| }}, |
| }}; |
| } |
| |
| bool Device::SetGain(fuchsia_hardware_audio::GainState& gain_state) { |
| ADR_LOG_METHOD(kLogStreamConfigFidlCalls); |
| FX_CHECK(state_ != State::DeviceInitializing); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return false; |
| } |
| if (!GetControlNotify()) { |
| ADR_WARN_METHOD() << "Device must be allocated before this method can be called"; |
| return false; |
| } |
| |
| auto status = (*stream_config_client_)->SetGain(std::move(gain_state)); |
| if (status.is_error()) { |
| if (status.error_value().is_canceled()) { |
| // These indicate that we are already shutting down, so they aren't error conditions. |
| ADR_LOG_METHOD(kLogStreamConfigFidlResponses) |
| << "SetGain response will take no action on error " << status.error_value(); |
| |
| return false; |
| } |
| |
| FX_PLOGS(ERROR, status.error_value().status()) << __func__ << " returned error:"; |
| OnError(status.error_value().status()); |
| return false; |
| } |
| |
| ADR_LOG_METHOD(kLogStreamConfigFidlResponses) << " is_ok"; |
| |
| // We don't notify anyone - we wait for the driver to notify us via WatchGainState. |
| return true; |
| } |
| |
| // If the optional<weak_ptr> is set AND the weak_ptr can be locked to its shared_ptr, then the |
| // resulting shared_ptr is returned. Otherwise, nullptr is returned, after first resetting the |
| // optional `control_notify` if it is set but the weak_ptr is no longer valid. |
| std::shared_ptr<ControlNotify> Device::GetControlNotify() { |
| if (!control_notify_) { |
| return nullptr; |
| } |
| |
| auto sh_ptr_control = control_notify_->lock(); |
| if (!sh_ptr_control) { |
| control_notify_.reset(); |
| LogObjectCounts(); |
| } |
| |
| return sh_ptr_control; |
| } |
| |
| void Device::SetDaiFormat(ElementId element_id, |
| const fuchsia_hardware_audio::DaiFormat& dai_format) { |
| ADR_LOG_METHOD(kLogCodecFidlCalls || kLogCompositeFidlCalls); |
| |
| auto notify = GetControlNotify(); |
| if (!notify) { |
| ADR_WARN_METHOD() << "Device must be controlled before SetDaiFormat can be called"; |
| return; |
| } |
| |
| if (!is_codec() && !is_composite()) { |
| ADR_WARN_METHOD() << "Incorrect device_type " << device_type_ << " cannot SetDaiFormat"; |
| notify->DaiFormatNotSet(element_id, dai_format, |
| fuchsia_audio_device::ControlSetDaiFormatError::kWrongDeviceType); |
| return; |
| } |
| |
| FX_CHECK(state_ != State::DeviceInitializing) |
| << "Cannot call SetDaiFormat before device is initialized"; |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| notify->DaiFormatNotSet(element_id, dai_format, |
| fuchsia_audio_device::ControlSetDaiFormatError::kDeviceError); |
| return; |
| } |
| |
| if (is_codec() ? (element_id != fuchsia_audio_device::kDefaultDaiInterconnectElementId) |
| : (dai_endpoint_ids_.find(element_id) == dai_endpoint_ids_.end())) { |
| ADR_WARN_METHOD() << "element_id not found, or not a DaiInterconnect Endpoint"; |
| notify->DaiFormatNotSet(element_id, dai_format, |
| fuchsia_audio_device::ControlSetDaiFormatError::kInvalidElementId); |
| return; |
| } |
| |
| if (!ValidateDaiFormat(dai_format)) { |
| ADR_WARN_METHOD() << "Invalid dai_format -- cannot SetDaiFormat"; |
| notify->DaiFormatNotSet(element_id, dai_format, |
| fuchsia_audio_device::ControlSetDaiFormatError::kInvalidDaiFormat); |
| return; |
| } |
| |
| if (!DaiFormatIsSupported(element_id, dai_format_sets(), dai_format)) { |
| ADR_WARN_METHOD() << "Unsupported dai_format cannot be set"; |
| notify->DaiFormatNotSet(element_id, dai_format, |
| fuchsia_audio_device::ControlSetDaiFormatError::kFormatMismatch); |
| return; |
| } |
| |
| // Check for no-change |
| |
| if (is_codec()) { |
| // auto new_dai_format = dai_format; |
| |
| // TODO(https://fxbug.dev/113429): handle command timeouts |
| |
| (*codec_client_) |
| ->SetDaiFormat(dai_format) |
| .Then([this, element_id, |
| dai_format](fidl::Result<fuchsia_hardware_audio::Codec::SetDaiFormat>& result) { |
| auto notify = GetControlNotify(); |
| if (!notify) { |
| ADR_WARN_OBJECT() |
| << "SetDaiFormat response: device must be controlled before SetDaiFormat can be called"; |
| return; |
| } |
| |
| if (state_ == State::Error) { |
| ADR_WARN_OBJECT() << "Codec/SetDaiFormat response: device already has an error"; |
| notify->DaiFormatNotSet(element_id, dai_format, |
| fuchsia_audio_device::ControlSetDaiFormatError::kDeviceError); |
| return; |
| } |
| |
| if (!result.is_ok()) { |
| fuchsia_audio_device::ControlSetDaiFormatError error; |
| // These types of errors don't lead us to mark the device as in Error state. |
| if (result.error_value().is_domain_error() && |
| (result.error_value().domain_error() == ZX_ERR_INVALID_ARGS || |
| result.error_value().domain_error() == ZX_ERR_NOT_SUPPORTED)) { |
| ADR_WARN_OBJECT() |
| << "Codec/SetDaiFormat response: ZX_ERR_INVALID_ARGS or ZX_ERR_NOT_SUPPORTED"; |
| error = (result.error_value().domain_error() == ZX_ERR_NOT_SUPPORTED |
| ? fuchsia_audio_device::ControlSetDaiFormatError::kFormatMismatch |
| : fuchsia_audio_device::ControlSetDaiFormatError::kInvalidDaiFormat); |
| } else { |
| LogResultError(result, "SetDaiFormat response"); |
| error = fuchsia_audio_device::ControlSetDaiFormatError::kOther; |
| } |
| notify->DaiFormatNotSet(element_id, dai_format, error); |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogCodecFidlResponses) << "Codec/SetDaiFormat: success"; |
| if (!ValidateCodecFormatInfo(result->state())) { |
| FX_LOGS(ERROR) << "Codec/SetDaiFormat error: " << result.error_value(); |
| OnError(ZX_ERR_INVALID_ARGS); |
| notify->DaiFormatNotSet( |
| element_id, dai_format, |
| fuchsia_audio_device::ControlSetDaiFormatError::kInvalidDaiFormat); |
| return; |
| } |
| |
| if (codec_format_ && codec_format_->dai_format == dai_format && |
| codec_format_->codec_format_info == result->state()) { |
| // No DaiFormat change for this element |
| notify->DaiFormatNotSet(element_id, dai_format, |
| fuchsia_audio_device::ControlSetDaiFormatError(0)); |
| return; |
| } |
| |
| // Reset Start state and DaiFormat (if it's a change). Notify our controlling entity. |
| bool was_started = codec_start_state_.started; |
| if (was_started) { |
| codec_start_state_.started = false; |
| codec_start_state_ = CodecStartState{false, zx::clock::get_monotonic()}; |
| } |
| codec_format_ = CodecFormat{dai_format, result->state()}; |
| if (was_started) { |
| notify->CodecStopped(codec_start_state_.start_stop_time); |
| } |
| notify->DaiFormatChanged(element_id, codec_format_->dai_format, |
| codec_format_->codec_format_info); |
| }); |
| } else { |
| // TODO(https://fxbug.dev/113429): handle command timeouts |
| |
| (*composite_client_) |
| ->SetDaiFormat({element_id, dai_format}) |
| .Then([this, element_id, |
| dai_format](fidl::Result<fuchsia_hardware_audio::Composite::SetDaiFormat>& result) { |
| std::string context("Composite/SetDaiFormat response: "); |
| auto notify = GetControlNotify(); |
| if (!notify) { |
| ADR_WARN_OBJECT() << context |
| << "device must be controlled before SetDaiFormat can be called"; |
| return; |
| } |
| |
| if (state_ == State::Error) { |
| ADR_WARN_OBJECT() << context << "device already has an error"; |
| notify->DaiFormatNotSet(element_id, dai_format, |
| fuchsia_audio_device::ControlSetDaiFormatError::kDeviceError); |
| return; |
| } |
| |
| if (!result.is_ok()) { |
| fuchsia_audio_device::ControlSetDaiFormatError error; |
| // These types of errors don't lead us to mark the device as in Error state. |
| if (result.error_value().is_domain_error() && |
| (result.error_value().domain_error() == |
| fuchsia_hardware_audio::DriverError::kInvalidArgs)) { |
| ADR_WARN_OBJECT() << context << "kInvalidArgs"; |
| error = fuchsia_audio_device::ControlSetDaiFormatError::kInvalidDaiFormat; |
| } else if (result.error_value().is_domain_error() && |
| result.error_value().domain_error() == |
| fuchsia_hardware_audio::DriverError::kNotSupported) { |
| ADR_WARN_OBJECT() << context << "kNotSupported"; |
| error = fuchsia_audio_device::ControlSetDaiFormatError::kFormatMismatch; |
| } else { |
| LogResultError(result, context.c_str()); |
| error = fuchsia_audio_device::ControlSetDaiFormatError::kOther; |
| } |
| notify->DaiFormatNotSet(element_id, dai_format, error); |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogCompositeFidlResponses) << "Composite/SetDaiFormat: success"; |
| if (auto match = composite_dai_formats_.find(element_id); |
| match != composite_dai_formats_.end() && match->second == dai_format) { |
| // No DaiFormat change for this element |
| notify->DaiFormatNotSet(element_id, dai_format, |
| fuchsia_audio_device::ControlSetDaiFormatError(0)); |
| return; |
| } |
| |
| // Unlike Codec, Composite DAI elements don't generate `CodecStopped` notifications. |
| composite_dai_formats_.insert_or_assign(element_id, dai_format); |
| notify->DaiFormatChanged(element_id, dai_format, std::nullopt); |
| }); |
| } |
| } |
| |
| bool Device::Reset() { |
| if (!is_codec() && !is_composite()) { |
| ADR_WARN_METHOD() << "Incorrect device_type " << device_type_ << " cannot Reset"; |
| return false; |
| } |
| |
| ADR_LOG_METHOD(kLogCodecFidlCalls); |
| FX_CHECK(state_ != State::DeviceInitializing); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return false; |
| } |
| if (!GetControlNotify()) { |
| ADR_WARN_METHOD() << "Codec must be controlled before Reset can be called"; |
| return false; |
| } |
| |
| // TODO(https://fxbug.dev/113429): handle command timeouts |
| |
| if (is_codec()) { |
| (*codec_client_) |
| ->Reset() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::Codec::Reset>& result) { |
| std::string context = "Codec/Reset response"; |
| if (LogResultFrameworkError(result, context.c_str())) { |
| return; |
| } |
| ADR_LOG_OBJECT(kLogCodecFidlResponses) << context; |
| |
| // Reset to Stopped (if Started), even if no ControlNotify listens for notifications. |
| if (codec_start_state_.started) { |
| codec_start_state_.started = false; |
| codec_start_state_.start_stop_time = zx::clock::get_monotonic(); |
| if (auto notify = GetControlNotify(); notify) { |
| notify->CodecStopped(codec_start_state_.start_stop_time); |
| } |
| } |
| |
| if (codec_format_) { |
| // Reset our DaiFormat, even if no ControlNotify listens for notifications. |
| codec_format_.reset(); |
| if (auto notify = GetControlNotify(); notify) { |
| notify->DaiFormatChanged(fuchsia_audio_device::kDefaultDaiInterconnectElementId, |
| std::nullopt, std::nullopt); |
| } |
| } |
| |
| // TODO(https://fxbug.dev/323270827): implement signalprocessing for Codec (topology, |
| // gain). When implemented, reset the signalprocessing topology and all elements, here. |
| }); |
| } |
| if (is_composite()) { |
| (*composite_client_) |
| ->Reset() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::Composite::Reset>& result) { |
| std::string context = "Composite/Reset response"; |
| if (LogResultError(result, context.c_str())) { |
| return; |
| } |
| ADR_LOG_OBJECT(kLogCompositeFidlResponses) << context; |
| |
| //// Expect DeviceDroppedRingBuffer(element_id) from the driver, for all RingBuffers. |
| //// We shouldn't need to format-reset them or reset any hanging gets or expressly |
| //// change their state in any way. |
| // |
| // ring_buffer_map_.clear(); |
| |
| // For each DAI node, clear its DaiFormat and notify our controlling entity. |
| |
| if (!composite_dai_formats_.empty()) { |
| // Reset our DaiFormat, even if no ControlNotify listens for notifications. |
| if (auto notify = GetControlNotify(); notify) { |
| for (const auto& [element_id, _] : composite_dai_formats_) { |
| notify->DaiFormatChanged(element_id, std::nullopt, std::nullopt); |
| } |
| } |
| composite_dai_formats_.clear(); |
| } |
| |
| //// Expect the driver's WatchTopology and WatchElementState hanging-gets to all fire. |
| //// We shouldn't need to touch the hardware in any way until then. |
| // |
| // TODO(https://fxbug.dev/323270827): implement signalprocessing |
| // current_topology_id_.reset(); |
| // for (auto& [element_id, element_record] : sig_proc_element_map_) { |
| // element_record.state.reset(); |
| // } |
| // |
| //// Maybe ObserverNotify should emit optionals for [Topology|ElementState]Changed? |
| // |
| // ForEachObserver([...](auto obs) { ... }); |
| }); |
| } |
| |
| return true; |
| } |
| |
| bool Device::CodecStart() { |
| if (!is_codec()) { |
| ADR_WARN_METHOD() << "Incorrect device_type " << device_type_ << " cannot Start"; |
| return false; |
| } |
| |
| ADR_LOG_METHOD(kLogCodecFidlCalls); |
| FX_CHECK(state_ != State::DeviceInitializing); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return false; |
| } |
| // TODO(https://fxbug.dev/113429): handle command timeouts |
| if (!GetControlNotify()) { |
| ADR_WARN_METHOD() << "Codec must be controlled before Start can be called"; |
| return false; |
| } |
| if (!codec_format_) { |
| ADR_WARN_METHOD() << "Format must be set before Start can be called"; |
| return false; |
| } |
| |
| if (codec_start_state_.started) { |
| ADR_LOG_METHOD(kLogCodecFidlCalls) << "Codec is already started; ignoring CodecStart command"; |
| return true; |
| } |
| |
| (*codec_client_) |
| ->Start() |
| .Then([this](fidl::Result<fuchsia_hardware_audio::Codec::Start>& result) { |
| if (LogResultFrameworkError(result, "Start response")) { |
| if (auto notify = GetControlNotify(); notify) { |
| notify->CodecNotStarted(); |
| } |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogCodecFidlResponses) << "Codec/Start: success"; |
| |
| // Notify our controlling entity, if this was a change. |
| if (!codec_start_state_.started || |
| codec_start_state_.start_stop_time.get() <= result->start_time()) { |
| codec_start_state_.started = true; |
| codec_start_state_.start_stop_time = zx::time(result->start_time()); |
| if (auto notify = GetControlNotify(); notify) { |
| notify->CodecStarted(codec_start_state_.start_stop_time); |
| } |
| } |
| }); |
| |
| return true; |
| } |
| |
| bool Device::CodecStop() { |
| if (!is_codec()) { |
| ADR_WARN_METHOD() << "Incorrect device_type " << device_type_ << " cannot Stop"; |
| return false; |
| } |
| |
| ADR_LOG_METHOD(kLogCodecFidlCalls); |
| FX_CHECK(state_ != State::DeviceInitializing); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return false; |
| } |
| if (!GetControlNotify()) { |
| ADR_WARN_METHOD() << "Codec must be controlled before Stop can be called"; |
| return false; |
| } |
| if (!codec_format_) { |
| ADR_WARN_METHOD() << "Format must be set before Stop can be called"; |
| return false; |
| } |
| |
| if (!codec_start_state_.started) { |
| ADR_LOG_METHOD(kLogCodecFidlCalls) << "Codec is already stopped; ignoring CodecStop command"; |
| return true; |
| } |
| |
| // TODO(https://fxbug.dev/113429): handle command timeouts |
| |
| (*codec_client_)->Stop().Then([this](fidl::Result<fuchsia_hardware_audio::Codec::Stop>& result) { |
| if (LogResultFrameworkError(result, "Stop response")) { |
| if (auto notify = GetControlNotify(); notify) { |
| notify->CodecNotStopped(); |
| } |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogCodecFidlResponses) << "Codec/Stop: success"; |
| |
| // Notify our controlling entity, if this was a change. |
| if (codec_start_state_.started || |
| codec_start_state_.start_stop_time.get() <= result->stop_time()) { |
| codec_start_state_.started = false; |
| codec_start_state_.start_stop_time = zx::time(result->stop_time()); |
| if (auto notify = GetControlNotify(); notify) { |
| notify->CodecStopped(codec_start_state_.start_stop_time); |
| } |
| } |
| }); |
| |
| return true; |
| } |
| |
| bool Device::CreateRingBuffer( |
| ElementId element_id, const fuchsia_hardware_audio::Format& format, |
| uint32_t requested_ring_buffer_bytes, |
| fit::callback<void( |
| fit::result<fuchsia_audio_device::ControlCreateRingBufferError, Device::RingBufferInfo>)> |
| create_ring_buffer_callback) { |
| ADR_LOG_METHOD(kLogRingBufferMethods); |
| if (!is_composite() && !is_stream_config()) { |
| ADR_WARN_METHOD() << "Wrong device type"; |
| create_ring_buffer_callback( |
| fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kWrongDeviceType)); |
| return false; |
| } |
| |
| if (!GetControlNotify()) { |
| ADR_WARN_METHOD() << "Device must be controlled before CreateRingBuffer can be called"; |
| create_ring_buffer_callback( |
| fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kOther)); |
| return false; |
| } |
| |
| if (ring_buffer_endpoint_ids_.find(element_id) == ring_buffer_endpoint_ids_.end()) { |
| create_ring_buffer_callback( |
| fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kInvalidElementId)); |
| return false; |
| } |
| |
| ring_buffer_map_.erase(element_id); |
| |
| // This method create the ring_buffer map entry, upon success. |
| if (auto status = ConnectRingBufferFidl(element_id, format); |
| status != fuchsia_audio_device::ControlCreateRingBufferError(0)) { |
| create_ring_buffer_callback(fit::error(status)); |
| return false; |
| } |
| |
| auto& ring_buffer = ring_buffer_map_.find(element_id)->second; |
| ring_buffer.requested_ring_buffer_bytes = requested_ring_buffer_bytes; |
| ring_buffer.create_ring_buffer_callback = std::move(create_ring_buffer_callback); |
| |
| RetrieveRingBufferProperties(element_id); |
| RetrieveDelayInfo(element_id); |
| |
| return true; |
| } |
| |
| // Here, we detect all the error cases that we can, before calling into the driver. If we call into |
| // the driver, we return "no error", otherwise we return an error code that can be returned to |
| // clients as the reason the CreateRingBuffer call failed. |
| fuchsia_audio_device::ControlCreateRingBufferError Device::ConnectRingBufferFidl( |
| ElementId element_id, fuchsia_hardware_audio::Format driver_format) { |
| ADR_LOG_METHOD(kLogRingBufferMethods || kLogRingBufferFidlCalls); |
| |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return fuchsia_audio_device::ControlCreateRingBufferError::kDeviceError; |
| } |
| |
| if (!ValidateRingBufferFormat(driver_format)) { |
| return fuchsia_audio_device::ControlCreateRingBufferError::kInvalidFormat; |
| } |
| |
| auto bytes_per_sample = driver_format.pcm_format()->bytes_per_sample(); |
| auto sample_format = driver_format.pcm_format()->sample_format(); |
| if (!ValidateSampleFormatCompatibility(bytes_per_sample, sample_format)) { |
| return fuchsia_audio_device::ControlCreateRingBufferError::kInvalidFormat; |
| } |
| |
| if (!RingBufferFormatIsSupported(element_id, element_ring_buffer_format_sets_, driver_format)) { |
| ADR_WARN_METHOD() << "RingBuffer not supported"; |
| return fuchsia_audio_device::ControlCreateRingBufferError::kFormatMismatch; |
| } |
| |
| auto [client, server] = fidl::Endpoints<fuchsia_hardware_audio::RingBuffer>::Create(); |
| |
| if (is_stream_config()) { |
| auto result = (*stream_config_client_)->CreateRingBuffer({driver_format, std::move(server)}); |
| if (!result.is_ok()) { |
| FX_PLOGS(ERROR, result.error_value().status()) << "StreamConfig/CreateRingBuffer failed"; |
| return fuchsia_audio_device::ControlCreateRingBufferError::kFormatMismatch; |
| } |
| } else { |
| (*composite_client_) |
| ->CreateRingBuffer({element_id, driver_format, std::move(server)}) |
| .Then([this](fidl::Result<fuchsia_hardware_audio::Composite::CreateRingBuffer>& result) { |
| std::string context{"Composite/CreateRingBuffer response"}; |
| if (LogResultError(result, context.c_str())) { |
| return; |
| } |
| ADR_LOG_OBJECT(kLogCompositeFidlResponses) << context; |
| }); |
| } |
| |
| std::optional<fuchsia_audio::SampleType> sample_type; |
| if (bytes_per_sample == 1 && |
| sample_format == fuchsia_hardware_audio::SampleFormat::kPcmUnsigned) { |
| sample_type = fuchsia_audio::SampleType::kUint8; |
| } else if (bytes_per_sample == 2 && |
| sample_format == fuchsia_hardware_audio::SampleFormat::kPcmSigned) { |
| sample_type = fuchsia_audio::SampleType::kInt16; |
| } else if (bytes_per_sample == 4 && |
| sample_format == fuchsia_hardware_audio::SampleFormat::kPcmSigned) { |
| sample_type = fuchsia_audio::SampleType::kInt32; |
| } else if (bytes_per_sample == 4 && |
| sample_format == fuchsia_hardware_audio::SampleFormat::kPcmFloat) { |
| sample_type = fuchsia_audio::SampleType::kFloat32; |
| } else if (bytes_per_sample == 8 && |
| sample_format == fuchsia_hardware_audio::SampleFormat::kPcmFloat) { |
| sample_type = fuchsia_audio::SampleType::kFloat64; |
| } |
| FX_CHECK(sample_type) |
| << "Invalid sample format was not detected in ValidateSampleFormatCompatibility"; |
| uint64_t active_channels_bitmask = |
| (1ull << driver_format.pcm_format()->number_of_channels()) - 1ull; |
| |
| RingBufferRecord ring_buffer_record{ |
| .ring_buffer_state = RingBufferState::NotCreated, |
| .ring_buffer_handler = |
| std::make_unique<EndpointFidlErrorHandler<fuchsia_hardware_audio::RingBuffer>>( |
| this, element_id, "RingBuffer"), |
| .vmo_format = {{ |
| .sample_type = *sample_type, |
| .channel_count = driver_format.pcm_format()->number_of_channels(), |
| .frames_per_second = driver_format.pcm_format()->frame_rate(), |
| // TODO(https://fxbug.dev/42168795): handle channel_layout when communicated from driver. |
| }}, |
| .driver_format = driver_format, |
| .active_channels_bitmask = active_channels_bitmask, |
| }; |
| ring_buffer_record.ring_buffer_client = fidl::Client<fuchsia_hardware_audio::RingBuffer>( |
| std::move(client), dispatcher_, ring_buffer_record.ring_buffer_handler.get()), |
| |
| ring_buffer_map_.insert_or_assign(element_id, std::move(ring_buffer_record)); |
| |
| (void)SetActiveChannels(element_id, active_channels_bitmask, [this](zx::result<zx::time> result) { |
| if (result.is_ok()) { |
| ADR_LOG_OBJECT(kLogRingBufferFidlResponses) << "RingBuffer/SetActiveChannels IS supported"; |
| return; |
| } |
| if (result.status_value() == ZX_ERR_NOT_SUPPORTED) { |
| ADR_LOG_OBJECT(kLogRingBufferFidlResponses) |
| << "RingBuffer/SetActiveChannels IS NOT supported"; |
| return; |
| } |
| ADR_WARN_OBJECT() << "RingBuffer/SetActiveChannels returned error: " << result.status_string(); |
| OnError(result.status_value()); |
| }); |
| SetRingBufferState(element_id, RingBufferState::Creating); |
| |
| return fuchsia_audio_device::ControlCreateRingBufferError(0); |
| } |
| |
| void Device::RetrieveRingBufferProperties(ElementId element_id) { |
| ADR_LOG_METHOD(kLogRingBufferMethods || kLogRingBufferFidlCalls); |
| |
| auto& ring_buffer = ring_buffer_map_.find(element_id)->second; |
| FX_CHECK(ring_buffer.ring_buffer_client.has_value() && |
| ring_buffer.ring_buffer_client->is_valid()); |
| |
| (*ring_buffer.ring_buffer_client) |
| ->GetProperties() |
| .Then([this, &ring_buffer, |
| element_id](fidl::Result<fuchsia_hardware_audio::RingBuffer::GetProperties>& result) { |
| if (LogResultFrameworkError(result, "RingBuffer/GetProperties response")) { |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogRingBufferFidlResponses) << "RingBuffer/GetProperties: success"; |
| if (!ValidateRingBufferProperties(result->properties())) { |
| FX_LOGS(ERROR) << "RingBuffer/GetProperties error: " << result.error_value(); |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| ring_buffer.ring_buffer_properties = result->properties(); |
| CheckForRingBufferReady(element_id); |
| }); |
| } |
| |
| void Device::RetrieveDelayInfo(ElementId element_id) { |
| ADR_LOG_METHOD(kLogRingBufferMethods || kLogRingBufferFidlCalls); |
| |
| auto& ring_buffer = ring_buffer_map_.find(element_id)->second; |
| FX_CHECK(ring_buffer.ring_buffer_client.has_value() && |
| ring_buffer.ring_buffer_client->is_valid()); |
| |
| (*ring_buffer.ring_buffer_client) |
| ->WatchDelayInfo() |
| .Then([this, &ring_buffer, |
| element_id](fidl::Result<fuchsia_hardware_audio::RingBuffer::WatchDelayInfo>& result) { |
| if (LogResultFrameworkError(result, "RingBuffer/WatchDelayInfo response")) { |
| return; |
| } |
| ADR_LOG_OBJECT(kLogRingBufferFidlResponses) << "RingBuffer/WatchDelayInfo: success"; |
| |
| if (!ValidateDelayInfo(result->delay_info())) { |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| ring_buffer.delay_info = result->delay_info(); |
| // If requested_ring_buffer_bytes_ is already set, but num_ring_buffer_frames_ isn't, then |
| // we're getting delay info as part of creating a ring buffer. Otherwise, |
| // requested_ring_buffer_bytes_ must be set separately before calling GetVmo. |
| if (ring_buffer.requested_ring_buffer_bytes && !ring_buffer.num_ring_buffer_frames) { |
| // Needed, to set requested_ring_buffer_frames_ before calling GetVmo. |
| CalculateRequiredRingBufferSizes(element_id); |
| |
| FX_CHECK(device_info_->clock_domain()); |
| const auto clock_position_notifications_per_ring = |
| *device_info_->clock_domain() == fuchsia_hardware_audio::kClockDomainMonotonic ? 0 |
| : 2; |
| GetVmo(element_id, static_cast<uint32_t>(ring_buffer.requested_ring_buffer_frames), |
| clock_position_notifications_per_ring); |
| } |
| |
| // Notify our controlling entity, if we have one. |
| if (auto notify = GetControlNotify(); notify) { |
| notify->DelayInfoChanged(element_id, |
| {{ |
| .internal_delay = ring_buffer.delay_info->internal_delay(), |
| .external_delay = ring_buffer.delay_info->external_delay(), |
| }}); |
| } |
| RetrieveDelayInfo(element_id); |
| }); |
| } |
| |
| void Device::GetVmo(ElementId element_id, uint32_t min_frames, |
| uint32_t position_notifications_per_ring) { |
| ADR_LOG_METHOD(kLogRingBufferMethods || kLogRingBufferFidlCalls); |
| |
| auto& ring_buffer = ring_buffer_map_.find(element_id)->second; |
| FX_CHECK(ring_buffer.ring_buffer_client.has_value() && |
| ring_buffer.ring_buffer_client->is_valid()); |
| FX_CHECK(ring_buffer.driver_format); |
| |
| (*ring_buffer.ring_buffer_client) |
| ->GetVmo({{.min_frames = min_frames, |
| .clock_recovery_notifications_per_ring = position_notifications_per_ring}}) |
| .Then([this, &ring_buffer, |
| element_id](fidl::Result<fuchsia_hardware_audio::RingBuffer::GetVmo>& result) { |
| if (LogResultError(result, "RingBuffer/GetVmo response")) { |
| return; |
| } |
| ADR_LOG_OBJECT(kLogRingBufferFidlResponses) << "RingBuffer/GetVmo: success"; |
| |
| if (!ValidateRingBufferVmo(result->ring_buffer(), result->num_frames(), |
| *ring_buffer.driver_format)) { |
| FX_PLOGS(ERROR, ZX_ERR_INVALID_ARGS) << "Error in RingBuffer/GetVmo response"; |
| OnError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| ring_buffer.ring_buffer_vmo = std::move(result->ring_buffer()); |
| ring_buffer.num_ring_buffer_frames = result->num_frames(); |
| CheckForRingBufferReady(element_id); |
| }); |
| } |
| |
| // RingBuffer FIDL successful-response handlers. |
| void Device::CheckForRingBufferReady(ElementId element_id) { |
| ADR_LOG_METHOD(kLogRingBufferFidlResponses); |
| if (state_ == State::Error) { |
| ADR_WARN_METHOD() << "device already has an error"; |
| return; |
| } |
| |
| auto& ring_buffer = ring_buffer_map_.find(element_id)->second; |
| // Check whether we are tearing down, or conversely have already set up the ring buffer. |
| if (ring_buffer.ring_buffer_state != RingBufferState::Creating) { |
| return; |
| } |
| |
| // We're creating the ring buffer but don't have all our prerequisites yet. |
| if (!ring_buffer.ring_buffer_properties || !ring_buffer.delay_info || |
| !ring_buffer.num_ring_buffer_frames) { |
| return; |
| } |
| |
| auto ref_clock = GetReadOnlyClock(); |
| if (!ref_clock.is_ok()) { |
| ADR_WARN_METHOD() << "reference clock is not ok"; |
| ring_buffer.create_ring_buffer_callback( |
| fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kDeviceError)); |
| ring_buffer.create_ring_buffer_callback = nullptr; |
| OnError(ZX_ERR_INTERNAL); |
| return; |
| } |
| |
| SetRingBufferState(element_id, RingBufferState::Stopped); |
| |
| FX_CHECK(ring_buffer.create_ring_buffer_callback); |
| ring_buffer.create_ring_buffer_callback(fit::success(Device::RingBufferInfo{ |
| .ring_buffer = fuchsia_audio::RingBuffer{{ |
| .buffer = fuchsia_mem::Buffer{{ |
| .vmo = std::move(ring_buffer.ring_buffer_vmo), |
| .size = *ring_buffer.num_ring_buffer_frames * ring_buffer.bytes_per_frame, |
| }}, |
| .format = ring_buffer.vmo_format, |
| .producer_bytes = ring_buffer.ring_buffer_producer_bytes, |
| .consumer_bytes = ring_buffer.ring_buffer_consumer_bytes, |
| .reference_clock = std::move(*ref_clock), |
| .reference_clock_domain = *device_info_->clock_domain(), |
| }}, |
| .properties = fuchsia_audio_device::RingBufferProperties{{ |
| .valid_bits_per_sample = valid_bits_per_sample(element_id), |
| .turn_on_delay = ring_buffer.ring_buffer_properties->turn_on_delay().value_or(0), |
| }}, |
| })); |
| ring_buffer.create_ring_buffer_callback = nullptr; |
| } |
| |
| // Returns TRUE if it actually calls out to the driver. It avoid doing so if it already knows |
| // that this RingBuffer does not support SetActiveChannels. |
| bool Device::SetActiveChannels( |
| ElementId element_id, uint64_t channel_bitmask, |
| fit::callback<void(zx::result<zx::time>)> set_active_channels_callback) { |
| ADR_LOG_METHOD(kLogRingBufferFidlCalls); |
| |
| auto& ring_buffer = ring_buffer_map_.find(element_id)->second; |
| FX_CHECK(ring_buffer.ring_buffer_client.has_value() && |
| ring_buffer.ring_buffer_client->is_valid()); |
| |
| // If we already know this device doesn't support SetActiveChannels, do nothing. |
| if (!ring_buffer.supports_set_active_channels.value_or(true)) { |
| return false; |
| } |
| (*ring_buffer.ring_buffer_client) |
| ->SetActiveChannels({{.active_channels_bitmask = channel_bitmask}}) |
| .Then( |
| [this, &ring_buffer, channel_bitmask, callback = std::move(set_active_channels_callback)]( |
| fidl::Result<fuchsia_hardware_audio::RingBuffer::SetActiveChannels>& result) mutable { |
| if (result.is_error() && result.error_value().is_domain_error() && |
| result.error_value().domain_error() == ZX_ERR_NOT_SUPPORTED) { |
| ADR_LOG_OBJECT(kLogRingBufferFidlResponses) |
| << "RingBuffer/SetActiveChannels: device does not support this method"; |
| ring_buffer.supports_set_active_channels = false; |
| callback(zx::error(ZX_ERR_NOT_SUPPORTED)); |
| return; |
| } |
| if (LogResultError(result, "RingBuffer/SetActiveChannels response")) { |
| ring_buffer.supports_set_active_channels = false; |
| callback(zx::error(ZX_ERR_INTERNAL)); |
| return; |
| } |
| |
| ADR_LOG_OBJECT(kLogRingBufferFidlResponses) << "RingBuffer/SetActiveChannels: success"; |
| |
| ring_buffer.supports_set_active_channels = true; |
| ring_buffer.active_channels_bitmask = channel_bitmask; |
| ring_buffer.active_channels_set_time = zx::time(result->set_time()); |
| callback(zx::ok(*ring_buffer.active_channels_set_time)); |
| LogActiveChannels(ring_buffer.active_channels_bitmask, |
| *ring_buffer.active_channels_set_time); |
| }); |
| return true; |
| } |
| |
| void Device::StartRingBuffer(ElementId element_id, |
| fit::callback<void(zx::result<zx::time>)> start_callback) { |
| ADR_LOG_METHOD(kLogRingBufferFidlCalls); |
| |
| auto& ring_buffer = ring_buffer_map_.find(element_id)->second; |
| FX_CHECK(ring_buffer.ring_buffer_client.has_value() && |
| ring_buffer.ring_buffer_client->is_valid()); |
| |
| (*ring_buffer.ring_buffer_client) |
| ->Start() |
| .Then([this, &ring_buffer, element_id, callback = std::move(start_callback)]( |
| fidl::Result<fuchsia_hardware_audio::RingBuffer::Start>& result) mutable { |
| if (LogResultFrameworkError(result, "RingBuffer/Start response")) { |
| callback(zx::error(ZX_ERR_INTERNAL)); |
| return; |
| } |
| ADR_LOG_OBJECT(kLogRingBufferFidlResponses) << "RingBuffer/Start: success"; |
| |
| ring_buffer.start_time = zx::time(result->start_time()); |
| callback(zx::ok(*ring_buffer.start_time)); |
| SetRingBufferState(element_id, RingBufferState::Started); |
| }); |
| } |
| |
| void Device::StopRingBuffer(ElementId element_id, fit::callback<void(zx_status_t)> stop_callback) { |
| ADR_LOG_METHOD(kLogRingBufferFidlCalls); |
| |
| auto& ring_buffer = ring_buffer_map_.find(element_id)->second; |
| FX_CHECK(ring_buffer.ring_buffer_client.has_value() && |
| ring_buffer.ring_buffer_client->is_valid()); |
| |
| (*ring_buffer.ring_buffer_client) |
| ->Stop() |
| .Then([this, &ring_buffer, element_id, callback = std::move(stop_callback)]( |
| fidl::Result<fuchsia_hardware_audio::RingBuffer::Stop>& result) mutable { |
| if (LogResultFrameworkError(result, "RingBuffer/Stop response")) { |
| callback(ZX_ERR_INTERNAL); |
| return; |
| } |
| ADR_LOG_OBJECT(kLogRingBufferFidlResponses) << "RingBuffer/Stop: success"; |
| |
| ring_buffer.start_time.reset(); |
| callback(ZX_OK); |
| SetRingBufferState(element_id, RingBufferState::Stopped); |
| }); |
| } |
| |
| // Uses the VMO format and ring buffer properties, to set bytes_per_frame, |
| // requested_ring_buffer_frames, ring_buffer_consumer_bytes and ring_buffer_producer_bytes. |
| void Device::CalculateRequiredRingBufferSizes(ElementId element_id) { |
| ADR_LOG_METHOD(kLogRingBufferMethods); |
| |
| auto& ring_buffer = ring_buffer_map_.find(element_id)->second; |
| FX_CHECK(ring_buffer.vmo_format.channel_count()); |
| FX_CHECK(ring_buffer.vmo_format.sample_type()); |
| FX_CHECK(ring_buffer.vmo_format.frames_per_second()); |
| FX_CHECK(ring_buffer.ring_buffer_properties); |
| FX_CHECK(ring_buffer.ring_buffer_properties->driver_transfer_bytes()); |
| FX_CHECK(ring_buffer.requested_ring_buffer_bytes); |
| FX_CHECK(*ring_buffer.requested_ring_buffer_bytes > 0); |
| |
| switch (*ring_buffer.vmo_format.sample_type()) { |
| case fuchsia_audio::SampleType::kUint8: |
| ring_buffer.bytes_per_frame = 1; |
| break; |
| case fuchsia_audio::SampleType::kInt16: |
| ring_buffer.bytes_per_frame = 2; |
| break; |
| case fuchsia_audio::SampleType::kInt32: |
| case fuchsia_audio::SampleType::kFloat32: |
| ring_buffer.bytes_per_frame = 4; |
| break; |
| case fuchsia_audio::SampleType::kFloat64: |
| ring_buffer.bytes_per_frame = 8; |
| break; |
| default: |
| FX_LOGS(FATAL) << __func__ << ": unknown fuchsia_audio::SampleType"; |
| __UNREACHABLE; |
| } |
| ring_buffer.bytes_per_frame *= *ring_buffer.vmo_format.channel_count(); |
| |
| ring_buffer.requested_ring_buffer_frames = |
| media::TimelineRate{1, ring_buffer.bytes_per_frame}.Scale( |
| *ring_buffer.requested_ring_buffer_bytes, media::TimelineRate::RoundingMode::Ceiling); |
| |
| // Determine whether the RingBuffer client is a Producer or a Consumer. |
| // If the RingBuffer element is "outgoing" (if it is a source in Topology edges), then the client |
| // indeed _produces_ the frames that populate the RingBuffer. |
| bool element_is_outgoing = false, element_is_incoming = false; |
| if (is_composite()) { |
| FX_CHECK(current_topology_id_.has_value()); |
| auto topology_match = sig_proc_topology_map_.find(*current_topology_id_); |
| FX_CHECK(topology_match != sig_proc_topology_map_.end()); |
| auto& topology = sig_proc_topology_map_.find(*current_topology_id_)->second; |
| for (auto& edge : topology) { |
| if (edge.processing_element_id_from() == element_id) { |
| element_is_outgoing = true; |
| } |
| if (edge.processing_element_id_to() == element_id) { |
| element_is_incoming = true; |
| } |
| } |
| FX_CHECK(element_is_outgoing != element_is_incoming); |
| } else if (is_stream_config_output()) { |
| element_is_outgoing = true; |
| } |
| |
| // We don't include driver transfer size in our VMO size request (requested_ring_buffer_frames_) |
| // ... but we do communicate it in our description of ring buffer producer/consumer "zones". |
| uint64_t driver_bytes = *ring_buffer.ring_buffer_properties->driver_transfer_bytes() + |
| ring_buffer.bytes_per_frame - 1; |
| driver_bytes -= (driver_bytes % ring_buffer.bytes_per_frame); |
| |
| if (element_is_outgoing) { |
| ring_buffer.ring_buffer_consumer_bytes = driver_bytes; |
| ring_buffer.ring_buffer_producer_bytes = |
| ring_buffer.requested_ring_buffer_frames * ring_buffer.bytes_per_frame; |
| } else { |
| ring_buffer.ring_buffer_producer_bytes = driver_bytes; |
| ring_buffer.ring_buffer_consumer_bytes = |
| ring_buffer.requested_ring_buffer_frames * ring_buffer.bytes_per_frame; |
| } |
| |
| // TODO(https://fxbug.dev/42069012): validate this case; we don't surface an error to the caller. |
| if (ring_buffer.requested_ring_buffer_frames > std::numeric_limits<uint32_t>::max()) { |
| ADR_WARN_METHOD() << "requested_ring_buffer_frames cannot exceed uint32_t::max()"; |
| ring_buffer.requested_ring_buffer_frames = std::numeric_limits<uint32_t>::max(); |
| } |
| } |
| |
| // TODO(https://fxbug.dev/42069013): implement, via RingBuffer/WatchClockRecoveryPositionInfo. |
| void Device::RecoverDeviceClockFromPositionInfo() { ADR_LOG_METHOD(kLogRingBufferMethods); } |
| |
| // TODO(https://fxbug.dev/42069013): implement this. |
| void Device::StopDeviceClockRecovery() { ADR_LOG_METHOD(kLogRingBufferMethods); } |
| |
| } // namespace media_audio |