blob: 09107a3b06f7d0874c2ce94622a481dd7a3b697a [file] [log] [blame]
// Copyright 2017 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/audio_plug_detector.h"
#include <dirent.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 <sys/stat.h>
#include <sys/types.h>
#include <zircon/compiler.h>
#include <zircon/device/audio.h>
#include <zircon/device/vfs.h>
#include <fbl/auto_lock.h>
#include <fbl/macros.h>
#include <fbl/unique_fd.h>
#include "src/lib/files/unique_fd.h"
#include "src/media/audio/audio_core/audio_device_manager.h"
#include "src/media/audio/audio_core/audio_input.h"
#include "src/media/audio/audio_core/audio_output.h"
#include "src/media/audio/audio_core/driver_output.h"
#include "src/media/audio/audio_core/reporter.h"
namespace media::audio {
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},
};
AudioPlugDetector::~AudioPlugDetector() { FXL_DCHECK(manager_ == nullptr); }
zx_status_t AudioPlugDetector::Start(AudioDeviceManager* manager) {
FXL_DCHECK(manager != nullptr);
// 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(); });
// If we are already running, we cannot start again. Cancel the cleanup
// operation and report that things are successfully started.
if (manager_ != nullptr) {
FXL_LOG(INFO) << "Attempted to start the AudioPlugDetector twice!";
error_cleanup.cancel();
return ZX_OK;
}
// Record our new manager
manager_ = manager;
// 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) {
FXL_LOG(ERROR) << "AudioPlugDetector 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 AudioPlugDetector::Stop() {
manager_ = nullptr;
watchers_.clear();
}
void AudioPlugDetector::AddAudioDevice(int dir_fd, const std::string& name, bool is_input) {
if (manager_ == nullptr)
return;
// Open the device node.
fbl::unique_fd dev_node(openat(dir_fd, name.c_str(), O_RDONLY));
if (!dev_node.is_valid()) {
REP(FailedToOpenDevice(name, is_input, errno));
FXL_LOG(ERROR) << "AudioPlugDetector 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));
FXL_PLOG(ERROR, res) << "Failed to obtain FDIO service channel to audio "
<< (is_input ? "input" : "output");
return;
}
// Obtain the stream channel
zx::channel channel;
fuchsia::hardware::audio::Device_SyncProxy dev(std::move(dev_channel));
res = dev.GetChannel(&channel);
if (res != ZX_OK) {
REP(FailedToObtainStreamChannel(name, is_input, res));
FXL_PLOG(ERROR, res) << "Failed to open channel to audio " << (is_input ? "input" : "output");
return;
}
manager_->AddDeviceByChannel(std::move(channel), name, is_input);
}
} // namespace media::audio