blob: 79e427247dffb2d7806487cec0dc05ec5ac2f8a5 [file] [log] [blame]
// Copyright 2023 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/audio_core/v2/device_watcher.h"
#include <fidl/fuchsia.audio.device/cpp/natural_types.h>
#include <fidl/fuchsia.audio.device/cpp/type_conversions.h>
#include <fidl/fuchsia.audio/cpp/natural_types.h>
#include <fidl/fuchsia.audio/cpp/type_conversions.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/compiler.h>
#include "src/media/audio/audio_core/shared/select_best_format.h"
namespace media_audio {
namespace {
#define USE_AUDIO_DEVICE_REGISTRY_DEVICES (1)
template <typename ResultT>
bool LogResultError(const ResultT& result, const char* debug_context) {
if (!result.ok()) {
FX_LOGS(WARNING) << debug_context << ": failed with transport error: " << result;
return true;
}
if (!result->is_ok()) {
FX_LOGS(ERROR) << debug_context
<< ": failed with code: " << fidl::ToUnderlying(result->error_value());
return true;
}
return false;
}
#if (USE_AUDIO_DEVICE_REGISTRY_DEVICES)
audio_stream_unique_id_t DeviceUniqueId(fuchsia_audio_device::wire::Info info) {
audio_stream_unique_id_t unique_id{0};
if (info.has_unique_instance_id()) {
std::memcpy(unique_id.data, info.unique_instance_id().data(), sizeof(unique_id.data));
}
return unique_id;
}
#endif // USE_AUDIO_DEVICE_REGISTRY_DEVICES
} // namespace
DeviceWatcher::DeviceWatcher(Args args)
: graph_client_(std::move(args.graph_client)),
registry_client_(fidl::WireSharedClient(std::move(args.registry_client), args.dispatcher)),
control_creator_client_(
fidl::WireSharedClient(std::move(args.control_creator_client), args.dispatcher)),
dispatcher_(args.dispatcher),
output_thread_(args.output_thread),
input_thread_(args.input_thread),
output_thread_profile_(args.output_thread_profile),
input_thread_profile_(args.input_thread_profile),
route_graph_(std::move(args.route_graph)),
effects_loader_(std::move(args.effects_loader)) {
WatchDevicesAdded();
WatchDevicesRemoved();
}
void DeviceWatcher::WatchDevicesAdded() {
registry_client_->WatchDevicesAdded().Then([this, self = shared_from_this()](auto& result) {
if (!LogResultError(result, "WatchDevicesAdded")) {
return;
}
if (!result->value()->has_devices()) {
FX_LOGS(ERROR) << "WatchDevicesAdded bug: response missing `devices`";
return;
}
for (auto device : result->value()->devices()) {
AddDevice(device);
}
WatchDevicesAdded();
});
}
void DeviceWatcher::WatchDevicesRemoved() {
registry_client_->WatchDeviceRemoved().Then([this, self = shared_from_this()](auto& result) {
if (!LogResultError(result, "WatchDeviceRemoved")) {
return;
}
if (!result->value()->has_token_id()) {
FX_LOGS(ERROR) << "WatchDeviceRemoved bug: response missing `devices`";
return;
}
RemoveDevice(result->value()->token_id());
WatchDevicesRemoved();
});
}
void DeviceWatcher::AddDevice(fuchsia_audio_device::wire::Info info) {
// Ignore invalid device infos.
if (!info.has_token_id() || !info.has_device_type() || !info.has_device_name() ||
!info.has_ring_buffer_format_sets() || info.ring_buffer_format_sets().empty() ||
!info.has_gain_caps() || !info.has_plug_detect_caps() || !info.has_clock_domain()) {
FX_LOGS(ERROR) << "fuchsia.audio.device.Info missing required field";
return;
}
#if USE_AUDIO_DEVICE_REGISTRY_DEVICES
for (const auto& format_sets_for_element : info.ring_buffer_format_sets()) {
if (!format_sets_for_element.has_element_id() || !format_sets_for_element.has_format_sets() ||
format_sets_for_element.format_sets().empty()) {
FX_LOGS(ERROR) << "fuchsia.audio.device.ElementRingBufferFormatSet missing required field";
return;
}
for (auto format : format_sets_for_element.format_sets()) {
if (!format.has_channel_sets() || format.channel_sets().empty() ||
!format.has_sample_types() || format.sample_types().empty() ||
!format.has_frame_rates() || format.frame_rates().empty()) {
FX_LOGS(ERROR) << "fuchsia.audio.device.PcmFormatSet missing required field";
return;
}
for (auto channel_set : format.channel_sets()) {
if (!channel_set.has_attributes() || channel_set.attributes().empty()) {
FX_LOGS(ERROR) << "fuchsia.audio.device.ChannelSet missing required field";
return;
}
}
}
}
#endif // USE_AUDIO_DEVICE_REGISTRY_DEVICES
if (!info.gain_caps().has_min_gain_db() || !info.gain_caps().has_max_gain_db() ||
!info.gain_caps().has_gain_step_db()) {
FX_LOGS(ERROR) << "fuchsia.audio.device.GainCapabilities missing required field";
return;
}
// Duplicates should not happen: this is a bug.
if (devices_.count(info.token_id()) > 0) {
FX_LOGS(ERROR) << "device with token_id '" << info.token_id() << "' already exists";
return;
}
if (info.device_type() != fuchsia_audio_device::DeviceType::kInput &&
info.device_type() != fuchsia_audio_device::DeviceType::kOutput) {
FX_LOGS(WARNING) << "ignoring device with unsupported device_type '"
<< fidl::ToUnderlying(info.device_type()) << "'";
return;
}
// Create the Device.
auto observer_endpoints = fidl::CreateEndpoints<fuchsia_audio_device::Observer>();
if (!observer_endpoints.is_ok()) {
FX_PLOGS(ERROR, observer_endpoints.status_value()) << "fidl::CreateEndpoints failed";
return;
}
auto control_endpoints = fidl::CreateEndpoints<fuchsia_audio_device::Control>();
if (!control_endpoints.is_ok()) {
FX_PLOGS(ERROR, control_endpoints.status_value()) << "fidl::CreateEndpoints failed";
return;
}
fidl::Arena<> arena;
registry_client_
->CreateObserver(fuchsia_audio_device::wire::RegistryCreateObserverRequest::Builder(arena)
.token_id(info.token_id())
.observer_server(std::move(observer_endpoints->server))
.Build())
.Then([this, self = shared_from_this(), token_id = info.token_id()](auto& result) {
if (!LogResultError(result, "CreateObserver")) {
RemoveDevice(token_id);
}
});
control_creator_client_
->Create(fuchsia_audio_device::wire::ControlCreatorCreateRequest::Builder(arena)
.token_id(info.token_id())
.control_server(std::move(control_endpoints->server))
.Build())
.Then([this, self = shared_from_this(), token_id = info.token_id()](auto& result) {
if (!LogResultError(result, "CreateControl")) {
RemoveDevice(token_id);
}
});
switch (info.device_type()) {
case fuchsia_audio_device::DeviceType::kInput:
AddInputDevice(info, std::move(observer_endpoints->client),
std::move(control_endpoints->client));
break;
case fuchsia_audio_device::DeviceType::kOutput:
AddOutputDevice(info, std::move(observer_endpoints->client),
std::move(control_endpoints->client));
break;
default:
__UNREACHABLE;
break;
}
}
void DeviceWatcher::AddOutputDevice(fuchsia_audio_device::wire::Info info,
fidl::ClientEnd<fuchsia_audio_device::Observer> observer_client,
fidl::ClientEnd<fuchsia_audio_device::Control> control_client) {
FX_CHECK(info.device_type() == fuchsia_audio_device::DeviceType::kOutput);
#if (USE_AUDIO_DEVICE_REGISTRY_DEVICES)
auto profile = config_.output_device_profile(DeviceUniqueId(info));
// The pipeline's preferred format, converted from media::audio::Format to media_audio::Format.
const auto pref_format_old = profile.pipeline_config().OutputFormat(effects_loader_.get());
const auto pref_format = Format::CreateLegacyOrDie(fuchsia_media::wire::AudioStreamType{
.sample_format =
static_cast<fuchsia_media::AudioSampleFormat>(pref_format_old.sample_format()),
.channels = pref_format_old.stream_type().channels,
.frames_per_second = pref_format_old.stream_type().frames_per_second,
});
// Select the format to use for this device.
const auto format = media::audio::SelectBestFormat(
*fidl::ToNatural(info.ring_buffer_format_sets()
.at(fuchsia_audio_device::kDefaultRingBufferElementId)
.format_sets()),
pref_format);
if (!format.is_ok()) {
FX_LOGS(WARNING) << "output device with token_id '" << info.token_id()
<< "' cannot select a format given the pipeline format '" << pref_format
<< "'";
return;
}
// If the selected rate or channelization differs from our pipeline's config, then:
// If the root pipeline stage has effects, the effects may not be compatible with the selected
// format, so fail; otherwise, adjust the root pipeline state to match the selected format.
if (format->frames_per_second() != pref_format.frames_per_second() ||
format->channels() != pref_format.channels()) {
if (profile.pipeline_config().root().effects_v2) {
FX_LOGS(WARNING) << "output device with token_id '" << info.token_id()
<< "' cannot use selected format '" << *format << "' with pipeline format '"
<< pref_format << "'";
return;
}
media::audio::PipelineConfig pipeline_config = profile.pipeline_config();
pipeline_config.mutable_root().output_rate = static_cast<int32_t>(format->frames_per_second());
pipeline_config.mutable_root().output_channels = static_cast<int16_t>(format->channels());
profile = media::audio::DeviceConfig::OutputDeviceProfile(
profile.eligible_for_loopback(), profile.supported_usages(), profile.volume_curve(),
profile.independent_volume_control(), pipeline_config, profile.driver_gain_db(),
profile.software_gain_db());
}
// Compute how many "producer bytes" we need for this device's ring buffer.
// These constants match ../v1/driver_output.cc.
constexpr auto kDefaultMaxRetentionNsec = zx::msec(60);
constexpr auto kDefaultRetentionGapNsec = zx::msec(10);
// This formula matches ../v1/driver_output.cc.
const auto high_water_duration = 2 * output_thread_profile_.period;
const auto producer_duration =
high_water_duration + kDefaultMaxRetentionNsec + kDefaultRetentionGapNsec;
devices_[info.token_id()] = Device::Create({
.graph_client = graph_client_,
.observer_client = std::move(observer_client),
.control_client = std::move(control_client),
.dispatcher = dispatcher_,
.info = info,
.format = *format,
.thread = output_thread_,
.config = std::move(profile),
.min_ring_buffer_bytes = format->bytes_per(producer_duration),
.route_graph = route_graph_,
.effects_loader = effects_loader_,
});
#else // USE_AUDIO_DEVICE_REGISTRY_DEVICES
FX_LOGS(WARNING) << "For now, AudioCoreV2 is not connecting to devices";
return;
#endif // USE_AUDIO_DEVICE_REGISTRY_DEVICES
}
void DeviceWatcher::AddInputDevice(fuchsia_audio_device::wire::Info info,
fidl::ClientEnd<fuchsia_audio_device::Observer> observer_client,
fidl::ClientEnd<fuchsia_audio_device::Control> control_client) {
FX_CHECK(info.device_type() == fuchsia_audio_device::DeviceType::kInput);
#if (USE_AUDIO_DEVICE_REGISTRY_DEVICES)
auto profile = config_.input_device_profile(DeviceUniqueId(info));
// Select the format to use for this device.
const auto pref_format = Format::CreateOrDie({
.sample_type = fuchsia_audio::SampleType::kInt16,
.channels = 1,
.frames_per_second = profile.rate(),
});
const auto format = media::audio::SelectBestFormat(
*fidl::ToNatural(info.ring_buffer_format_sets()
.at(fuchsia_audio_device::kDefaultRingBufferElementId)
.format_sets()),
pref_format);
if (!format.is_ok()) {
FX_LOGS(WARNING) << "input device with token_id '" << info.token_id()
<< "' cannot select a format given the pipeline format '" << pref_format
<< "'";
return;
}
// Compute how many "consumer bytes" we need for this device's ring buffer.
// These constants match ../v1/audio_input.cc.
constexpr zx::duration kMinFenceDistance = zx::msec(200);
constexpr zx::duration kMaxFenceDistance = kMinFenceDistance + zx::msec(20);
devices_[info.token_id()] = Device::Create({
.graph_client = graph_client_,
.observer_client = std::move(observer_client),
.control_client = std::move(control_client),
.dispatcher = dispatcher_,
.info = info,
.format = *format,
.thread = input_thread_,
.config = std::move(profile),
.min_ring_buffer_bytes = format->bytes_per(kMaxFenceDistance),
.route_graph = route_graph_,
.effects_loader = effects_loader_,
});
#else // USE_AUDIO_DEVICE_REGISTRY_DEVICES
FX_LOGS(WARNING) << "For now, AudioCoreV2 is not connecting to devices";
return;
#endif // USE_AUDIO_DEVICE_REGISTRY_DEVICES
}
void DeviceWatcher::RemoveDevice(TokenId token_id) {
auto it = devices_.find(token_id);
if (it == devices_.end()) {
FX_LOGS(WARNING) << "device with token_id '" << token_id << "' does not exist";
return;
}
it->second->Destroy();
devices_.erase(it);
}
} // namespace media_audio