blob: 48d418869331576a7d95e8c52702715e545acf8d [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/media/audio/audio_core/plug_detector.h"
#include <fcntl.h>
#include <fuchsia/hardware/audio/cpp/fidl.h>
#include <lib/fdio/fdio.h>
#include <lib/fit/defer.h>
#include <lib/zx/channel.h>
#include <zircon/compiler.h>
#include <memory>
#include <vector>
#include <fbl/unique_fd.h>
#include <trace/event.h>
#include "src/lib/fsl/io/device_watcher.h"
#include "src/lib/syslog/cpp/logger.h"
#include "src/media/audio/audio_core/reporter.h"
namespace media::audio {
namespace {
static const struct {
const char* path;
bool is_input;
} AUDIO_DEVNODES[] = {
{.path = "/dev/class/audio-output", .is_input = false},
{.path = "/dev/class/audio-input", .is_input = true},
};
class PlugDetectorImpl : public PlugDetector {
public:
zx_status_t Start(Observer observer) final {
TRACE_DURATION("audio", "PlugDetectorImpl::Start");
// Start should only be called once.
FX_DCHECK(watchers_.empty());
FX_DCHECK(!observer_);
FX_DCHECK(observer);
observer_ = std::move(observer);
// If we fail to set up monitoring for any of our target directories,
// automatically stop monitoring all sources of device nodes.
auto error_cleanup = fit::defer([this]() { Stop(); });
// Create our watchers.
for (const auto& devnode : AUDIO_DEVNODES) {
auto watcher = fsl::DeviceWatcher::Create(
devnode.path,
[this, is_input = devnode.is_input](int dir_fd, const std::string& filename) {
AddAudioDevice(dir_fd, filename, is_input);
});
if (watcher == nullptr) {
FX_LOGS(ERROR) << "PlugDetectorImpl failed to create DeviceWatcher for \"" << devnode.path
<< "\".";
return ZX_ERR_NO_MEMORY;
}
watchers_.emplace_back(std::move(watcher));
}
error_cleanup.cancel();
return ZX_OK;
}
void Stop() final {
TRACE_DURATION("audio", "PlugDetectorImpl::Stop");
observer_ = nullptr;
watchers_.clear();
}
private:
void AddAudioDevice(int dir_fd, const std::string& name, bool is_input) {
TRACE_DURATION("audio", "PlugDetectorImpl::AddAudioDevice");
if (!observer_) {
return;
}
// Open the device node.
//
// TODO(35145): Remove blocking 'openat' from the main thread. fdio_open_at is probably what we
// want, but we'll need a version of DeviceWatcher that operates on fuchsia::io::Directory
// handles instead of file descriptors.
fbl::unique_fd dev_node(openat(dir_fd, name.c_str(), O_RDONLY));
if (!dev_node.is_valid()) {
REP(FailedToOpenDevice(name, is_input, errno));
FX_LOGS(ERROR) << "PlugDetectorImpl failed to open device node at \"" << name << "\". ("
<< strerror(errno) << " : " << errno << ")";
return;
}
// Obtain the FDIO device channel, wrap it in a sync proxy, use that to get the stream channel.
zx_status_t res;
zx::channel dev_channel;
res = fdio_get_service_handle(dev_node.release(), dev_channel.reset_and_get_address());
if (res != ZX_OK) {
REP(FailedToObtainFdioServiceChannel(name, is_input, res));
FX_PLOGS(ERROR, res) << "Failed to obtain FDIO service channel to audio "
<< (is_input ? "input" : "output");
return;
}
// Obtain the stream channel
auto device =
fidl::InterfaceHandle<fuchsia::hardware::audio::Device>(std::move(dev_channel)).Bind();
device.set_error_handler([name, is_input](zx_status_t res) {
REP(FailedToObtainStreamChannel(name, is_input, res));
FX_PLOGS(ERROR, res) << "Failed to open channel to audio " << (is_input ? "input" : "output");
});
device->GetChannel([d = std::move(device), this, is_input, name](
::fidl::InterfaceHandle<fuchsia::hardware::audio::StreamConfig> intf) {
observer_(intf.TakeChannel(), name, is_input);
});
}
Observer observer_;
std::vector<std::unique_ptr<fsl::DeviceWatcher>> watchers_;
};
} // namespace
std::unique_ptr<PlugDetector> PlugDetector::Create() {
return std::make_unique<PlugDetectorImpl>();
}
} // namespace media::audio