blob: 751f66e9bd2ee66086010b482aeac01f1493cfb4 [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/device_detector.h"
#include <fcntl.h>
#include <fidl/fuchsia.audio.device/cpp/common_types.h>
#include <fidl/fuchsia.audio.device/cpp/natural_types.h>
#include <fidl/fuchsia.hardware.audio/cpp/fidl.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fidl/cpp/wire/internal/transport_channel.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/channel.h>
#include <memory>
#include <vector>
#include <fbl/unique_fd.h>
#include "src/lib/fsl/io/device_watcher.h"
#include "src/media/audio/services/device_registry/logging.h"
namespace media_audio {
using fuchsia_audio_device::DeviceType;
using fuchsia_audio_device::DriverClient;
namespace {
struct DeviceNodeSpecifier {
const char* path;
DeviceType device_type;
};
constexpr DeviceNodeSpecifier kAudioDevNodes[] = {
{.path = "/dev/class/audio-composite", .device_type = DeviceType::kComposite},
{.path = "/dev/class/audio-input", .device_type = DeviceType::kInput},
{.path = "/dev/class/audio-output", .device_type = DeviceType::kOutput},
{.path = "/dev/class/codec", .device_type = DeviceType::kCodec},
};
} // namespace
zx::result<std::shared_ptr<DeviceDetector>> DeviceDetector::Create(DeviceDetectionHandler handler,
async_dispatcher_t* dispatcher) {
// The constructor is private, forcing clients to use DeviceDetector::Create().
class MakePublicCtor : public DeviceDetector {
public:
MakePublicCtor(DeviceDetectionHandler handler, async_dispatcher_t* dispatcher)
: DeviceDetector(std::move(handler), dispatcher) {}
};
auto detector = std::make_shared<MakePublicCtor>(std::move(handler), dispatcher);
if (auto status = detector->StartDeviceWatchers(); status != ZX_OK) {
return zx::error(status);
}
return zx::ok(detector);
}
zx_status_t DeviceDetector::StartDeviceWatchers() {
// StartDeviceWatchers should never be called a second time.
FX_CHECK(watchers_.empty());
FX_CHECK(dispatcher_);
for (const auto& dev_node : kAudioDevNodes) {
auto watcher = fsl::DeviceWatcher::Create(
dev_node.path,
[this, device_type = dev_node.device_type](
const fidl::ClientEnd<fuchsia_io::Directory>& dir, const std::string& filename) {
if (!dispatcher_) {
FX_LOGS(ERROR) << "DeviceWatcher fired but dispatcher is gone";
return;
}
if (device_type == DeviceType::kCodec || device_type == DeviceType::kComposite ||
device_type == DeviceType::kInput || device_type == DeviceType::kOutput) {
DriverClientFromDevFs(dir, filename, device_type);
} else {
FX_LOGS(WARNING) << device_type << " device detection not yet supported";
return;
}
},
dispatcher_);
// If any of our directory-monitors cannot be created, destroy them all and fail.
if (watcher == nullptr) {
FX_LOGS(ERROR) << "DeviceDetector failed to create DeviceWatcher for '" << dev_node.path
<< "'; stopping all device monitoring.";
watchers_.clear();
handler_ = nullptr;
return ZX_ERR_INTERNAL;
}
watchers_.emplace_back(std::move(watcher));
}
return ZX_OK;
}
void DeviceDetector::DriverClientFromDevFs(const fidl::ClientEnd<fuchsia_io::Directory>& dir,
const std::string& name, DeviceType device_type) {
FX_CHECK(handler_);
std::optional<fuchsia_audio_device::DriverClient> driver_client;
if (device_type == fuchsia_audio_device::DeviceType::kCodec) {
zx::result client_end = component::ConnectAt<fuchsia_hardware_audio::CodecConnector>(dir, name);
if (client_end.is_error()) {
FX_PLOGS(ERROR, client_end.error_value())
<< "DeviceDetector failed to connect to device node at '" << name << "'";
return;
}
fidl::Client connector(std::move(client_end.value()), dispatcher_);
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_audio::Codec>();
if (!endpoints.is_ok()) {
FX_LOGS(ERROR) << "DriverClientFromDevFs: CreateEndpoints failed";
return;
}
auto status = connector->Connect(std::move(endpoints->server));
if (!status.is_ok()) {
FX_PLOGS(ERROR, status.error_value().status())
<< "Connector/Connect failed for " << device_type;
return;
}
driver_client = DriverClient::WithCodec(std::move(endpoints->client));
} else if (device_type == fuchsia_audio_device::DeviceType::kComposite) {
// Composite devices such as aml-g12-tdm are DFv2: we can connect directly to them.
// TODO(https://fxbug.dev/304551042): Convert VirtualAudioComposite to DFv2; remove 'else'.
if constexpr (kDetectDFv2CompositeDevices) {
zx::result client_end = component::ConnectAt<fuchsia_hardware_audio::Composite>(dir, name);
if (client_end.is_error()) {
FX_PLOGS(ERROR, client_end.error_value())
<< "DeviceDetector failed to connect to DFv2 Composite node at '" << name << "'";
return;
}
driver_client = DriverClient::WithComposite(std::move(client_end.value()));
}
// The VirtualAudioComposite implementation is DFv1: connect via CompositeConnector.
else {
zx::result client_end =
component::ConnectAt<fuchsia_hardware_audio::CompositeConnector>(dir, name);
if (client_end.is_error()) {
FX_PLOGS(ERROR, client_end.error_value())
<< "DeviceDetector failed to connect to device node at '" << name << "'";
return;
}
fidl::Client connector(std::move(client_end.value()), dispatcher_);
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_audio::Composite>();
if (!endpoints.is_ok()) {
FX_LOGS(ERROR) << "DriverClientFromDevFs: CreateEndpoints failed";
return;
}
auto status = connector->Connect(std::move(endpoints->server));
if (!status.is_ok()) {
FX_PLOGS(ERROR, status.error_value().status())
<< "Connector/Connect failed for " << device_type;
return;
}
driver_client = DriverClient::WithComposite(std::move(endpoints->client));
}
} else if (device_type == fuchsia_audio_device::DeviceType::kInput ||
device_type == fuchsia_audio_device::DeviceType::kOutput) {
zx::result client_end =
component::ConnectAt<fuchsia_hardware_audio::StreamConfigConnector>(dir, name);
if (client_end.is_error()) {
FX_PLOGS(ERROR, client_end.error_value())
<< "DeviceDetector failed to connect to device node at '" << name << "'";
return;
}
fidl::Client connector(std::move(client_end.value()), dispatcher_);
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_audio::StreamConfig>();
if (!endpoints.is_ok()) {
FX_LOGS(ERROR) << "DriverClientFromDevFs: CreateEndpoints failed";
return;
}
auto status = connector->Connect(std::move(endpoints->server));
if (!status.is_ok()) {
FX_PLOGS(ERROR, status.error_value().status())
<< "Connector/Connect failed for " << device_type;
return;
}
driver_client = DriverClient::WithStreamConfig(std::move(endpoints->client));
} else {
FX_LOGS(WARNING) << device_type << " device detection not yet supported";
return;
}
if constexpr (kLogDeviceDetection) {
FX_LOGS(INFO) << "Detected and connected to " << device_type << " '" << name << "'";
}
handler_(name, device_type, std::move(*driver_client));
}
} // namespace media_audio