blob: 59e5547b08a095523ab405655e31b302b591eb56 [file] [log] [blame]
// 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/control_server.h"
#include <fidl/fuchsia.audio.device/cpp/fidl.h>
#include <fidl/fuchsia.audio/cpp/fidl.h>
#include <fidl/fuchsia.hardware.audio.signalprocessing/cpp/fidl.h>
#include <fidl/fuchsia.hardware.audio/cpp/fidl.h>
#include <fidl/fuchsia.mem/cpp/natural_types.h>
#include <lib/fidl/cpp/enum.h>
#include <lib/fidl/cpp/wire/status.h>
#include <lib/fit/internal/result.h>
#include <lib/fit/result.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/errors.h>
#include <optional>
#include <utility>
#include "lib/zx/result.h"
#include "src/media/audio/services/device_registry/device.h"
#include "src/media/audio/services/device_registry/logging.h"
#include "src/media/audio/services/device_registry/ring_buffer_server.h"
#include "src/media/audio/services/device_registry/validate.h"
namespace media_audio {
// static
std::shared_ptr<ControlServer> ControlServer::Create(
std::shared_ptr<const FidlThread> thread,
fidl::ServerEnd<fuchsia_audio_device::Control> server_end,
std::shared_ptr<AudioDeviceRegistry> parent, std::shared_ptr<Device> device) {
ADR_LOG_STATIC(kLogControlServerMethods);
return BaseFidlServer::Create(std::move(thread), std::move(server_end), std::move(parent),
std::move(device));
}
ControlServer::ControlServer(std::shared_ptr<AudioDeviceRegistry> parent,
std::shared_ptr<Device> device)
: parent_(std::move(parent)), device_(std::move(device)) {
ADR_LOG_METHOD(kLogObjectLifetimes);
++count_;
LogObjectCounts();
}
ControlServer::~ControlServer() {
ADR_LOG_METHOD(kLogObjectLifetimes);
--count_;
LogObjectCounts();
}
// Called when the client shuts down first.
void ControlServer::OnShutdown(fidl::UnbindInfo info) {
ADR_LOG_METHOD(kLogObjectLifetimes);
if (!info.is_peer_closed() && !info.is_user_initiated()) {
ADR_WARN_METHOD() << "shutdown with unexpected status: " << info;
} else {
ADR_LOG_METHOD(kLogRingBufferFidlResponses || kLogObjectLifetimes) << "with status: " << info;
}
for (auto& [_, weak_ring_buffer_server] : ring_buffer_servers_) {
if (auto sh_ring_buffer_server = weak_ring_buffer_server.lock(); sh_ring_buffer_server) {
sh_ring_buffer_server->ClientDroppedControl();
}
}
ring_buffer_servers_.clear();
}
// Called when Device drops its RingBuffer FIDL. Tell RingBufferServer and drop our reference.
void ControlServer::DeviceDroppedRingBuffer(ElementId element_id) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogNotifyMethods);
auto ring_buffer_server_iter = ring_buffer_servers_.find(element_id);
if (ring_buffer_server_iter != ring_buffer_servers_.end()) {
if (auto sh_ring_buffer_server = ring_buffer_server_iter->second.lock();
sh_ring_buffer_server) {
sh_ring_buffer_server->DeviceDroppedRingBuffer();
}
ring_buffer_servers_.erase(element_id);
}
}
void ControlServer::DeviceHasError() {
ADR_LOG_METHOD(kLogControlServerMethods);
device_has_error_ = true;
DeviceIsRemoved();
}
// Upon exiting this method, we drop our connection to the client.
void ControlServer::DeviceIsRemoved() {
ADR_LOG_METHOD(kLogControlServerMethods);
for (auto& [_, weak_ring_buffer_server] : ring_buffer_servers_) {
if (auto sh_ring_buffer_server = weak_ring_buffer_server.lock(); sh_ring_buffer_server) {
sh_ring_buffer_server->ClientDroppedControl();
}
}
ring_buffer_servers_.clear();
// We don't explicitly clear our shared_ptr<Device> reference, to ensure we destruct first.
Shutdown(ZX_ERR_PEER_CLOSED);
}
std::shared_ptr<RingBufferServer> ControlServer::TryGetRingBufferServer(ElementId element_id) {
ADR_LOG_METHOD(kLogControlServerMethods);
auto ring_buffer_server_iter = ring_buffer_servers_.find(element_id);
if (ring_buffer_server_iter != ring_buffer_servers_.end()) {
if (auto sh_ring_buffer_server = ring_buffer_server_iter->second.lock();
sh_ring_buffer_server) {
return sh_ring_buffer_server;
}
ring_buffer_servers_.erase(element_id);
}
return nullptr;
}
// fuchsia.audio.device.Control implementation
void ControlServer::SetGain(SetGainRequest& request, SetGainCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogControlServerMethods);
if (device_has_error_) {
ADR_WARN_METHOD() << "device has an error";
completer.Reply(fit::error(fuchsia_audio_device::ControlSetGainError::kDeviceError));
return;
}
if (!device_->is_stream_config()) {
ADR_WARN_METHOD() << "Unsupported method for device_type " << device_->device_type();
completer.Reply(fit::error(fuchsia_audio_device::ControlSetGainError::kWrongDeviceType));
return;
}
if (!request.target_state()) {
ADR_WARN_METHOD() << "required field 'target_state' is missing";
completer.Reply(fit::error(fuchsia_audio_device::ControlSetGainError::kInvalidGainState));
return;
}
auto& gain_caps = *device_->info()->gain_caps();
if (!request.target_state()->gain_db()) {
ADR_WARN_METHOD() << "required field `target_state.gain_db` is missing";
completer.Reply(fit::error(fuchsia_audio_device::ControlSetGainError::kInvalidGainDb));
return;
}
if (*request.target_state()->gain_db() > *gain_caps.max_gain_db() ||
*request.target_state()->gain_db() < *gain_caps.min_gain_db()) {
ADR_WARN_METHOD() << "gain_db (" << *request.target_state()->gain_db() << ") is out of range ["
<< *gain_caps.min_gain_db() << ", " << *gain_caps.max_gain_db() << "]";
completer.Reply(fit::error(fuchsia_audio_device::ControlSetGainError::kGainOutOfRange));
return;
}
if (request.target_state()->muted().value_or(false) && !(*gain_caps.can_mute())) {
ADR_WARN_METHOD() << "device cannot MUTE";
completer.Reply(fit::error(fuchsia_audio_device::ControlSetGainError::kMuteUnavailable));
return;
}
if (request.target_state()->agc_enabled().value_or(false) && !(*gain_caps.can_agc())) {
ADR_WARN_METHOD() << "device cannot AGC";
completer.Reply(fit::error(fuchsia_audio_device::ControlSetGainError::kAgcUnavailable));
return;
}
fuchsia_hardware_audio::GainState gain_state{{.gain_db = *request.target_state()->gain_db()}};
if (request.target_state()->muted()) {
gain_state.muted(*request.target_state()->muted());
}
if (request.target_state()->agc_enabled()) {
gain_state.agc_enabled(*request.target_state()->agc_enabled());
}
device_->SetGain(gain_state);
completer.Reply(fit::success(fuchsia_audio_device::ControlSetGainResponse{}));
}
void ControlServer::CreateRingBuffer(CreateRingBufferRequest& request,
CreateRingBufferCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogControlServerMethods);
// Fail if device has error.
if (device_has_error_) {
ADR_WARN_METHOD() << "device has an error";
completer.Reply(fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kDeviceError));
return;
}
if (!device_->is_stream_config() && !device_->is_composite()) {
ADR_WARN_METHOD() << "Unsupported method for device_type " << device_->device_type();
completer.Reply(
fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kWrongDeviceType));
return;
}
ElementId element_id = fuchsia_audio_device::kDefaultRingBufferElementId;
// Fail on missing parameters.
if (device_->is_composite()) {
if (!request.element_id()) {
ADR_WARN_METHOD() << "required field 'element_id' is missing";
completer.Reply(
fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kInvalidElementId));
return;
}
element_id = *request.element_id();
auto& rb_ids = device_->ring_buffer_endpoint_ids();
if (rb_ids.find(element_id) == rb_ids.end()) {
ADR_WARN_METHOD() << "required field 'element_id' (" << element_id
<< ") does not refer to an ENDPOINT of type RING_BUFFER";
completer.Reply(
fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kInvalidElementId));
return;
}
}
if (create_ring_buffer_completers_.find(element_id) != create_ring_buffer_completers_.end()) {
ADR_WARN_METHOD() << "(element_id " << element_id
<< ") previous `CreateRingBuffer` request has not completed";
completer.Reply(
fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kAlreadyPending));
return;
}
if (!request.options()) {
ADR_WARN_METHOD() << "(element_id " << element_id << ") required field 'options' is missing";
completer.Reply(
fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kInvalidOptions));
return;
}
if (!request.options()->format() || !request.options()->format()->sample_type() ||
!request.options()->format()->channel_count() ||
!request.options()->format()->frames_per_second()) {
ADR_WARN_METHOD() << "(element_id " << element_id
<< ") required 'options.format' (or one of its required members) is missing";
completer.Reply(fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kInvalidFormat));
return;
}
if (!request.options()->ring_buffer_min_bytes()) {
ADR_WARN_METHOD() << "(element_id " << element_id
<< ") required field 'options.ring_buffer_min_bytes' is missing";
completer.Reply(
fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kInvalidMinBytes));
return;
}
if (!request.ring_buffer_server()) {
ADR_WARN_METHOD() << "(element_id " << element_id
<< ") required field 'ring_buffer_server' is missing";
completer.Reply(
fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kInvalidRingBuffer));
return;
}
if (TryGetRingBufferServer(element_id)) {
ADR_WARN_METHOD() << "(element_id " << element_id << ") device RingBuffer already exists";
completer.Reply(
fit::error(fuchsia_audio_device::wire::ControlCreateRingBufferError::kAlreadyAllocated));
return;
}
auto driver_format =
device_->SupportedDriverFormatForClientFormat(element_id, *request.options()->format());
// Fail if device cannot satisfy the requested format.
if (!driver_format) {
ADR_WARN_METHOD() << "(element_id " << element_id
<< ") device does not support the specified options";
completer.Reply(
fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kFormatMismatch));
return;
}
create_ring_buffer_completers_.insert_or_assign(element_id, completer.ToAsync());
bool created = device_->CreateRingBuffer(
element_id, *driver_format, *request.options()->ring_buffer_min_bytes(),
[this, element_id](
fit::result<fuchsia_audio_device::ControlCreateRingBufferError, Device::RingBufferInfo>
result) {
// If we have no async completer, maybe we're shutting down. Just exit.
if (create_ring_buffer_completers_.find(element_id) ==
create_ring_buffer_completers_.end()) {
ADR_WARN_OBJECT()
<< "(element_id " << element_id
<< ") create_ring_buffer_completer_ gone by the time the CreateRingBuffer callback ran";
device_->DropRingBuffer(element_id); // Let this unwind the RingBufferServer.
return;
}
auto completer = std::move(create_ring_buffer_completers_.find(element_id)->second);
create_ring_buffer_completers_.erase(element_id);
if (result.is_error()) {
// Based on the driver response, set our ControlServer response
completer.Reply(
fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kDeviceError));
return;
}
completer.Reply(fit::success(fuchsia_audio_device::ControlCreateRingBufferResponse{{
.properties = result.value().properties,
.ring_buffer = std::move(result.value().ring_buffer),
}}));
});
if (!created) {
ADR_WARN_METHOD() << "device cannot create a ring buffer with the specified options";
auto completer = std::move(create_ring_buffer_completers_.find(element_id)->second);
create_ring_buffer_completers_.erase(element_id);
completer.Reply(fidl::Response<fuchsia_audio_device::Control::CreateRingBuffer>(
fit::error(fuchsia_audio_device::ControlCreateRingBufferError::kBadRingBufferOption)));
return;
}
auto ring_buffer_server =
RingBufferServer::Create(thread_ptr(), std::move(*request.ring_buffer_server()),
shared_from_this(), device_, element_id);
AddChildServer(ring_buffer_server);
ring_buffer_servers_.insert_or_assign(element_id, ring_buffer_server);
}
// This is only here because ControlNotify includes the methods from ObserverNotify. It might be
// helpful for ControlServer to know when its SetGain call took effect, but this isn't needed.
// ControlServer also has no gain-related hanging-get to complete.
void ControlServer::GainStateChanged(const fuchsia_audio_device::GainState&) {
ADR_LOG_METHOD(kLogNotifyMethods);
}
// This is only here because ControlNotify includes the methods from ObserverNotify. ControlServer
// doesn't have a role to play in plug state changes, nor a client hanging-get to complete.
void ControlServer::PlugStateChanged(const fuchsia_audio_device::PlugState& new_plug_state,
zx::time plug_change_time) {
ADR_LOG_METHOD(kLogNotifyMethods);
}
// We receive delay values for the first time during the configuration process. Once we have these
// values, we can calculate the required ring-buffer size and request the VMO.
void ControlServer::DelayInfoChanged(ElementId element_id,
const fuchsia_audio_device::DelayInfo& delay_info) {
ADR_LOG_METHOD(kLogControlServerResponses || kLogNotifyMethods);
// Initialization is complete, so this represents a delay update.
// If this is eventually exposed to Observers or any other watcher, notify them.
if (auto ring_buffer_server = TryGetRingBufferServer(element_id); ring_buffer_server) {
ring_buffer_server->DelayInfoChanged(delay_info);
}
}
void ControlServer::SetDaiFormat(SetDaiFormatRequest& request,
SetDaiFormatCompleter::Sync& completer) {
if (device_has_error_) {
ADR_WARN_METHOD() << "device has an error";
completer.Reply(fit::error(fuchsia_audio_device::ControlSetDaiFormatError::kDeviceError));
return;
}
ElementId element_id = fuchsia_audio_device::kDefaultDaiInterconnectElementId;
// Fail on missing parameters.
if (device_->is_composite()) {
if (!request.element_id().has_value()) {
ADR_WARN_METHOD() << "required field 'element_id' is missing";
completer.Reply(
fit::error(fuchsia_audio_device::ControlSetDaiFormatError::kInvalidElementId));
return;
}
element_id = *request.element_id();
} else if (!device_->is_codec()) {
ADR_WARN_METHOD() << "Unsupported method for device_type " << device_->device_type();
completer.Reply(fit::error(fuchsia_audio_device::ControlSetDaiFormatError::kWrongDeviceType));
return;
}
if (set_dai_format_completers_.find(element_id) != set_dai_format_completers_.end()) {
ADR_WARN_METHOD() << "previous `SetDaiFormat` request has not yet completed";
completer.Reply(fit::error(fuchsia_audio_device::ControlSetDaiFormatError::kAlreadyPending));
return;
}
if (!request.dai_format().has_value() || !ValidateDaiFormat(*request.dai_format())) {
ADR_WARN_METHOD() << "required field 'dai_format' is missing or invalid";
completer.Reply(fit::error(fuchsia_audio_device::ControlSetDaiFormatError::kInvalidDaiFormat));
return;
}
set_dai_format_completers_.insert_or_assign(element_id, completer.ToAsync());
device_->SetDaiFormat(element_id, *request.dai_format());
// We need the CodecFormatInfo to complete this, so we wait for the Device to notify us. Besides,
// it's also possible that the underlying driver will reject the request (e.g. format mismatch).
}
// The Device's DaiFormat has changed. If `dai_format` is set, this resulted from `SetDaiFormat`
// being called. Otherwise, the Device is newly-initialized or `Reset` was called, so
// SetDaiFormat must be called again.
void ControlServer::DaiFormatChanged(
ElementId element_id, const std::optional<fuchsia_hardware_audio::DaiFormat>& dai_format,
const std::optional<fuchsia_hardware_audio::CodecFormatInfo>& codec_format_info) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogNotifyMethods) << "(" << element_id << ")";
auto completer_match = set_dai_format_completers_.find(element_id);
// Device must be newly-initialized or Reset was called. Either way we don't expect a completion.
if (!dai_format.has_value()) {
FX_DCHECK(!codec_format_info.has_value());
// If there's a completer, it must have been in-progress when Reset was called; cancel it.
if (completer_match != set_dai_format_completers_.end()) {
auto completer = std::move(completer_match->second);
set_dai_format_completers_.erase(element_id);
completer.Reply(fit::error(fuchsia_audio_device::ControlSetDaiFormatError::kOther));
}
return;
}
// SetDaiFormat was called and succeeded, but now we don't have a completer.
if (completer_match == set_dai_format_completers_.end()) {
ADR_WARN_METHOD() << "received Device notification, but completer is gone.";
return;
}
auto completer = std::move(set_dai_format_completers_.find(element_id)->second);
set_dai_format_completers_.erase(element_id);
if (codec_format_info.has_value()) {
completer.Reply(fit::success(
fuchsia_audio_device::ControlSetDaiFormatResponse{{.state = *codec_format_info}}));
} else {
completer.Reply(
fit::success(fuchsia_audio_device::ControlSetDaiFormatResponse{{.state = std::nullopt}}));
}
}
void ControlServer::DaiFormatNotSet(ElementId element_id,
const fuchsia_hardware_audio::DaiFormat& dai_format,
fuchsia_audio_device::ControlSetDaiFormatError error) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogNotifyMethods)
<< "(" << element_id << ", error " << fidl::ToUnderlying(error) << ") for dai_format:";
LogDaiFormat(dai_format);
// SetDaiFormat was called, but now we don't have a completer.
if (set_dai_format_completers_.find(element_id) == set_dai_format_completers_.end()) {
ADR_WARN_METHOD()
<< "SetDaiFormat was called, did not result in change, but completer is gone.";
return;
}
auto completer = std::move(set_dai_format_completers_.find(element_id)->second);
set_dai_format_completers_.erase(element_id);
// If `error` is 0, SetDaiFormat was not an error but resulted in no change, so succeed that call.
if (error == fuchsia_audio_device::ControlSetDaiFormatError(0)) {
completer.Reply(fit::success(fuchsia_audio_device::ControlSetDaiFormatResponse{
{.state = device_->codec_format_info(element_id)}}));
} else {
completer.Reply(fit::error(error));
}
}
void ControlServer::CodecStart(CodecStartCompleter::Sync& completer) {
if (device_has_error_) {
ADR_WARN_METHOD() << "device has an error";
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStartError::kDeviceError));
return;
}
if (!device_->is_codec()) {
ADR_WARN_METHOD() << "Unsupported method for device_type " << device_->device_type();
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStartError::kWrongDeviceType));
return;
}
// Check for already pending
if (codec_start_completer_.has_value()) {
ADR_WARN_METHOD() << "previous `CodecStart` request has not yet completed";
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStartError::kAlreadyPending));
return;
}
if (!device_->dai_format_is_set()) {
ADR_WARN_METHOD() << "CodecStart called before DaiFormat was set";
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStartError::kDaiFormatNotSet));
return;
}
// Check for already started
if (device_->codec_is_started()) {
ADR_WARN_METHOD() << "Codec is already started";
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStartError::kAlreadyStarted));
return;
}
codec_start_completer_ = completer.ToAsync();
// Call into the Device to Start.
if (!device_->CodecStart()) {
auto start_completer = std::move(*codec_start_completer_);
codec_start_completer_.reset();
start_completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStartError::kDeviceError));
}
// We need `start_time` to complete this, so we wait for the Device to notify us. Besides,
// it's also possible that the underlying driver will reject the request.
}
void ControlServer::CodecStarted(const zx::time& start_time) {
ADR_LOG_METHOD(kLogNotifyMethods) << "(" << start_time.get() << ")";
if (!codec_start_completer_.has_value()) {
ADR_WARN_METHOD() << "received notification from Device, but completer is gone";
return;
}
auto completer = std::move(*codec_start_completer_);
codec_start_completer_.reset();
completer.Reply(fit::success(
fuchsia_audio_device::ControlCodecStartResponse{{.start_time = start_time.get()}}));
}
void ControlServer::CodecNotStarted() {
ADR_LOG_METHOD(kLogNotifyMethods);
if (!codec_start_completer_.has_value()) {
ADR_WARN_METHOD()
<< "received Device notification (CodecStart rejected), but completer is gone.";
return;
}
auto completer = std::move(*codec_start_completer_);
codec_start_completer_.reset();
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStartError::kOther));
}
void ControlServer::CodecStop(CodecStopCompleter::Sync& completer) {
if (device_has_error_) {
ADR_WARN_METHOD() << "device has an error";
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStopError::kDeviceError));
return;
}
if (!device_->is_codec()) {
ADR_WARN_METHOD() << "Unsupported method for device_type " << device_->device_type();
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStopError::kWrongDeviceType));
return;
}
// Check for already pending
if (codec_stop_completer_.has_value()) {
ADR_WARN_METHOD() << "previous `CodecStop` request has not yet completed";
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStopError::kAlreadyPending));
return;
}
if (!device_->dai_format_is_set()) {
ADR_WARN_METHOD() << "CodecStop called before DaiFormat was set";
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStopError::kDaiFormatNotSet));
return;
}
// Check for already stopped
if (!device_->codec_is_started()) {
ADR_WARN_METHOD() << "Codec is already stopped";
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStopError::kAlreadyStopped));
return;
}
codec_stop_completer_ = completer.ToAsync();
// Call into the Device to Stop.
if (!device_->CodecStop()) {
auto stop_completer = std::move(*codec_stop_completer_);
codec_stop_completer_.reset();
stop_completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStopError::kDeviceError));
return;
}
// We need `stop_time` to complete this, so we wait for the Device to notify us. Besides,
// it's also possible that the underlying driver will reject the request.
}
void ControlServer::CodecStopped(const zx::time& stop_time) {
ADR_LOG_METHOD(kLogNotifyMethods) << "(" << stop_time.get() << ")";
if (!codec_stop_completer_.has_value()) {
ADR_LOG_METHOD(kLogNotifyMethods)
<< "received Device notification, but completer is gone. Reset occurred?";
return;
}
auto completer = std::move(*codec_stop_completer_);
codec_stop_completer_.reset();
completer.Reply(
fit::success(fuchsia_audio_device::ControlCodecStopResponse{{.stop_time = stop_time.get()}}));
}
void ControlServer::CodecNotStopped() {
ADR_LOG_METHOD(kLogNotifyMethods);
if (!codec_stop_completer_.has_value()) {
ADR_WARN_METHOD()
<< "received Device notification (CodecStop rejected), but completer is gone.";
return;
}
auto completer = std::move(*codec_stop_completer_);
codec_stop_completer_.reset();
completer.Reply(fit::error(fuchsia_audio_device::ControlCodecStopError::kOther));
}
void ControlServer::Reset(ResetCompleter::Sync& completer) {
if (device_has_error_) {
ADR_WARN_METHOD() << "device has an error";
completer.Reply(fit::error(fuchsia_audio_device::ControlResetError::kDeviceError));
return;
}
if (!device_->is_codec() && !device_->is_composite()) {
ADR_WARN_METHOD() << "Unsupported method for device_type " << device_->device_type();
completer.Reply(fit::error(fuchsia_audio_device::ControlResetError::kWrongDeviceType));
return;
}
if (!device_->Reset()) {
ADR_WARN_METHOD() << "device had an error during Device::Reset";
completer.Reply(fit::error(fuchsia_audio_device::ControlResetError::kDeviceError));
return;
}
completer.Reply(fit::success(fuchsia_audio_device::ControlResetResponse{}));
}
// fuchsia.hardware.audio.signalprocessing.SignalProcessing support
//
void ControlServer::GetTopologies(GetTopologiesCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogControlServerMethods);
if (device_has_error_) {
ADR_WARN_METHOD() << "Device has error";
completer.Reply(zx::error(ZX_ERR_INTERNAL));
return;
}
FX_CHECK(device_);
if (!device_->is_codec() && !device_->is_composite() && !device_->is_stream_config()) {
ADR_WARN_METHOD() << "This device_type does not support " << __func__;
completer.Reply(zx::error(ZX_ERR_WRONG_TYPE));
return;
}
if (!device_->supports_signalprocessing()) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogSignalProcessingFidlCalls)
<< "This driver does not support signalprocessing";
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
return;
}
FX_CHECK(device_->info().has_value() &&
device_->info()->signal_processing_topologies().has_value() &&
!device_->info()->signal_processing_topologies()->empty());
completer.Reply(zx::ok(*device_->info()->signal_processing_topologies()));
}
void ControlServer::WatchTopology(WatchTopologyCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogControlServerMethods);
if (device_has_error_) {
ADR_WARN_METHOD() << "Device has error";
completer.Close(ZX_ERR_INTERNAL);
return;
}
FX_CHECK(device_);
if (!device_->is_codec() && !device_->is_composite() && !device_->is_stream_config()) {
ADR_WARN_METHOD() << "This device_type does not support " << __func__;
completer.Close(ZX_ERR_WRONG_TYPE);
return;
}
if (!device_->supports_signalprocessing()) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogSignalProcessingFidlCalls)
<< "This driver does not support signalprocessing";
completer.Close(ZX_ERR_NOT_SUPPORTED);
return;
}
if (watch_topology_completer_) {
ADR_WARN_METHOD() << "previous `WatchTopology` request has not yet completed";
completer.Close(ZX_ERR_BAD_STATE);
return;
}
FX_CHECK(!watch_topology_completer_.has_value());
watch_topology_completer_ = completer.ToAsync();
FX_CHECK(watch_topology_completer_.has_value());
MaybeCompleteWatchTopology();
}
void ControlServer::SetTopology(SetTopologyRequest& request,
SetTopologyCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogControlServerMethods);
if (device_has_error_) {
ADR_WARN_METHOD() << "Device has error";
completer.Reply(zx::error(ZX_ERR_INTERNAL));
return;
}
FX_CHECK(device_);
if (!device_->is_codec() && !device_->is_composite() && !device_->is_stream_config()) {
ADR_WARN_METHOD() << "unsupported method for this device_type";
completer.Reply(zx::error(ZX_ERR_WRONG_TYPE));
return;
}
if (!device_->supports_signalprocessing()) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogSignalProcessingFidlCalls)
<< "This driver does not support signalprocessing";
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
return;
}
if (device_->topology_ids().find(request.topology_id()) == device_->topology_ids().end()) {
ADR_WARN_METHOD() << "Unknown topology_id " << request.topology_id();
completer.Reply(zx::error(ZX_ERR_INVALID_ARGS));
return;
}
if (auto status = device_->SetTopology(request.topology_id()); status == ZX_OK) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogSignalProcessingFidlCalls)
<< "SetTopology succeeded";
completer.Reply(zx::ok());
} else {
ADR_LOG_METHOD(kLogControlServerMethods || kLogSignalProcessingFidlCalls)
<< "SetTopology failed: " << status;
completer.Reply(zx::error(status));
}
}
void ControlServer::TopologyChanged(TopologyId topology_id) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogNotifyMethods)
<< "(topology_id " << topology_id << ")";
topology_id_to_notify_ = topology_id;
MaybeCompleteWatchTopology();
}
void ControlServer::MaybeCompleteWatchTopology() {
if (watch_topology_completer_ && topology_id_to_notify_) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogNotifyMethods)
<< "completing(" << *topology_id_to_notify_ << ")";
auto completer = std::move(*watch_topology_completer_);
watch_topology_completer_.reset();
auto new_topology_id = *topology_id_to_notify_;
topology_id_to_notify_.reset();
completer.Reply({new_topology_id});
} else {
ADR_LOG_METHOD(kLogControlServerMethods || kLogNotifyMethods) << "NOT completing";
}
}
void ControlServer::GetElements(GetElementsCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogObserverServerMethods);
if (device_has_error_) {
ADR_WARN_METHOD() << "Device has error";
completer.Reply(zx::error(ZX_ERR_INTERNAL));
return;
}
FX_CHECK(device_);
if (!device_->is_codec() && !device_->is_composite() && !device_->is_stream_config()) {
ADR_WARN_METHOD() << "This device_type does not support " << __func__;
completer.Reply(zx::error(ZX_ERR_WRONG_TYPE));
return;
}
if (!device_->supports_signalprocessing()) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogSignalProcessingFidlCalls)
<< "This driver does not support signalprocessing";
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
return;
}
FX_CHECK(device_->info().has_value() &&
device_->info()->signal_processing_elements().has_value() &&
!device_->info()->signal_processing_elements()->empty());
completer.Reply(zx::ok(*device_->info()->signal_processing_elements()));
}
void ControlServer::WatchElementState(WatchElementStateRequest& request,
WatchElementStateCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogControlServerMethods);
if (device_has_error_) {
ADR_WARN_METHOD() << "Device has error";
completer.Close(ZX_ERR_INTERNAL);
return;
}
FX_CHECK(device_);
if (!device_->is_codec() && !device_->is_composite() && !device_->is_stream_config()) {
ADR_WARN_METHOD() << "This device_type does not support " << __func__;
completer.Close(ZX_ERR_WRONG_TYPE);
return;
}
if (!device_->supports_signalprocessing()) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogSignalProcessingFidlCalls)
<< "This driver does not support signalprocessing";
completer.Close(ZX_ERR_NOT_SUPPORTED);
return;
}
if (device_->element_ids().find(request.processing_element_id()) ==
device_->element_ids().end()) {
ADR_WARN_METHOD() << "Unknown element_id " << request.processing_element_id();
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
ElementId element_id = request.processing_element_id();
if (watch_element_state_completers_.find(element_id) != watch_element_state_completers_.end()) {
ADR_WARN_METHOD() << "previous `WatchElementState(" << element_id
<< ")` request has not yet completed";
completer.Close(ZX_ERR_BAD_STATE);
return;
}
watch_element_state_completers_.insert({element_id, completer.ToAsync()});
MaybeCompleteWatchElementState(element_id);
}
void ControlServer::SetElementState(SetElementStateRequest& request,
SetElementStateCompleter::Sync& completer) {
ADR_LOG_METHOD(kLogControlServerMethods)
<< "(element_id " << request.processing_element_id() << ")";
if (device_has_error_) {
ADR_WARN_METHOD() << "Device has error";
completer.Reply(zx::error(ZX_ERR_INTERNAL));
return;
}
FX_CHECK(device_);
if (!device_->is_codec() && !device_->is_composite() && !device_->is_stream_config()) {
ADR_WARN_METHOD() << "unsupported method for this device_type";
completer.Reply(zx::error(ZX_ERR_WRONG_TYPE));
return;
}
if (!device_->supports_signalprocessing()) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogSignalProcessingFidlCalls)
<< "This driver does not support signalprocessing";
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
return;
}
if (device_->element_ids().find(request.processing_element_id()) ==
device_->element_ids().end()) {
ADR_WARN_METHOD() << "Unknown element_id " << request.processing_element_id();
completer.Reply(zx::error(ZX_ERR_INVALID_ARGS));
return;
}
if (auto status = device_->SetElementState(request.processing_element_id(), request.state());
status == ZX_OK) {
completer.Reply(zx::ok());
} else {
completer.Reply(zx::error(status));
}
}
void ControlServer::ElementStateChanged(
ElementId element_id, fuchsia_hardware_audio_signalprocessing::ElementState element_state) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogNotifyMethods)
<< "(element_id " << element_id << ")";
element_states_to_notify_.insert_or_assign(element_id, element_state);
MaybeCompleteWatchElementState(element_id);
}
// If we have an outstanding hanging-get and a state-change, respond with the state change.
void ControlServer::MaybeCompleteWatchElementState(ElementId element_id) {
if (watch_element_state_completers_.find(element_id) != watch_element_state_completers_.end() &&
element_states_to_notify_.find(element_id) != element_states_to_notify_.end()) {
ADR_LOG_METHOD(kLogControlServerMethods || kLogNotifyMethods) << element_id << ": completing";
auto completer = std::move(watch_element_state_completers_.find(element_id)->second);
watch_element_state_completers_.erase(element_id);
auto new_element_state = element_states_to_notify_.find(element_id)->second;
element_states_to_notify_.erase(element_id);
completer.Reply({new_element_state});
} else {
ADR_LOG_METHOD(kLogControlServerMethods || kLogNotifyMethods)
<< element_id << ": NOT completing";
}
}
} // namespace media_audio