blob: cf98123aeffa210779baa914373e356d053aee27 [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 "garnet/bin/media/audio_core/audio_plug_detector.h"
#include <dirent.h>
#include <fbl/auto_lock.h>
#include <fbl/macros.h>
#include <fcntl.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/device.h>
#include <zircon/device/vfs.h>
#include "garnet/bin/media/audio_core/audio_device_manager.h"
#include "garnet/bin/media/audio_core/audio_input.h"
#include "garnet/bin/media/audio_core/audio_output.h"
#include "garnet/bin/media/audio_core/driver_output.h"
#include "lib/fxl/files/unique_fd.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_DLOG(WARNING) << "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, 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.
fxl::UniqueFD dev_node(::openat(dir_fd, name.c_str(), O_RDONLY));
if (!dev_node.is_valid()) {
FXL_LOG(WARNING) << "AudioPlugDetector failed to open device node at \""
<< name << "\". (" << strerror(errno) << " : " << errno
<< ")";
return;
}
// Obtain the stream channel
zx::channel channel;
ssize_t res;
res =
ioctl_audio_get_channel(dev_node.get(), channel.reset_and_get_address());
if (res < 0) {
FXL_LOG(WARNING) << "Failed to open channel to AudioOutput (res " << res
<< ")";
return;
}
// Hand the stream off to the proper type of class to manage.
fbl::RefPtr<AudioDevice> new_device;
if (is_input) {
new_device = AudioInput::Create(std::move(channel), manager_);
} else {
new_device = DriverOutput::Create(std::move(channel), manager_);
}
if (new_device == nullptr) {
FXL_LOG(WARNING) << "Failed to instantiate audio "
<< (is_input ? "input" : "output") << " for \"" << name
<< "\"";
} else {
manager_->AddDevice(std::move(new_device));
}
}
} // namespace media::audio