blob: 15339f2ceb267b4389aeb6289cc2f82df0505e9f [file] [log] [blame]
// Copyright 2016 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_device_manager.h"
#include <lib/fit/promise.h>
#include <lib/fit/single_threaded_executor.h>
#include <lib/trace/event.h>
#include <string>
#include "src/media/audio/audio_core/audio_core_impl.h"
#include "src/media/audio/audio_core/base_capturer.h"
#include "src/media/audio/audio_core/base_renderer.h"
#include "src/media/audio/audio_core/driver_output.h"
#include "src/media/audio/audio_core/plug_detector.h"
#include "src/media/audio/audio_core/reporter.h"
#include "src/media/audio/lib/logging/logging.h"
namespace media::audio {
AudioDeviceManager::AudioDeviceManager(ThreadingModel& threading_model,
std::unique_ptr<PlugDetector> plug_detector,
RouteGraph& route_graph, LinkMatrix& link_matrix,
ProcessConfig& process_config)
: threading_model_(threading_model),
route_graph_(route_graph),
plug_detector_(std::move(plug_detector)),
link_matrix_(link_matrix),
process_config_(process_config) {}
AudioDeviceManager::~AudioDeviceManager() {
Shutdown();
FX_DCHECK(devices_.empty());
}
// Configure this admin singleton object to manage audio device instances.
zx_status_t AudioDeviceManager::Init() {
TRACE_DURATION("audio", "AudioDeviceManager::Init");
// Start monitoring for plug/unplug events of pluggable audio output devices.
zx_status_t res =
plug_detector_->Start(fit::bind_member(this, &AudioDeviceManager::AddDeviceByVersion));
if (res != ZX_OK) {
FX_PLOGS(ERROR, res) << "AudioDeviceManager failed to start plug detector";
return res;
}
return ZX_OK;
}
// We are no longer managing audio devices, unwind everything.
void AudioDeviceManager::Shutdown() {
TRACE_DURATION("audio", "AudioDeviceManager::Shutdown");
plug_detector_->Stop();
std::vector<fit::promise<void>> device_promises;
for (auto& [_, device] : devices_pending_init_) {
device_promises.push_back(device->Shutdown());
}
devices_pending_init_.clear();
for (auto& [_, device] : devices_) {
device_promises.push_back(device->Shutdown());
}
devices_.clear();
fit::run_single_threaded(fit::join_promise_vector(std::move(device_promises)));
}
fit::promise<void, fuchsia::media::audio::UpdateEffectError> AudioDeviceManager::UpdateEffect(
const std::string& instance_name, const std::string& config, bool persist) {
if (persist) {
persisted_effects_updates_[instance_name] = config;
}
std::vector<fit::promise<void, fuchsia::media::audio::UpdateEffectError>> promises;
for (auto& [_, device] : devices_) {
promises.push_back(device->UpdateEffect(instance_name, config));
}
return fit::join_promise_vector(std::move(promises))
.then(
[](fit::result<std::vector<fit::result<void, fuchsia::media::audio::UpdateEffectError>>>&
results) -> fit::result<void, fuchsia::media::audio::UpdateEffectError> {
FX_DCHECK(results.is_ok()) << "fit::join_promise_vector returns an error";
bool found = false;
for (const auto& result : results.value()) {
if (result.is_error() &&
result.error() == fuchsia::media::audio::UpdateEffectError::INVALID_CONFIG) {
return result;
}
if (result.is_ok()) {
found = true;
}
}
if (found) {
return fit::ok();
} else {
return fit::error(fuchsia::media::audio::UpdateEffectError::NOT_FOUND);
}
});
}
fit::promise<void, fuchsia::media::audio::UpdateEffectError> AudioDeviceManager::UpdateDeviceEffect(
const std::string device_id, const std::string& instance_name, const std::string& message) {
auto devices = GetDeviceInfos();
const auto dev = std::find_if(devices.begin(), devices.end(), [&device_id](auto candidate) {
return candidate.unique_id == device_id;
});
if (dev == devices.end()) {
return fit::make_error_promise(fuchsia::media::audio::UpdateEffectError::NOT_FOUND);
}
auto device = devices_[dev->token_id];
FX_DCHECK(device);
return device->UpdateEffect(instance_name, message)
.then([](fit::result<void, fuchsia::media::audio::UpdateEffectError>& result)
-> fit::result<void, fuchsia::media::audio::UpdateEffectError> {
if (result.is_ok()) {
return fit::ok();
}
if (result.error() == fuchsia::media::audio::UpdateEffectError::INVALID_CONFIG) {
return result;
} else {
return fit::error(fuchsia::media::audio::UpdateEffectError::NOT_FOUND);
}
});
}
fit::promise<void, zx_status_t> AudioDeviceManager::UpdatePipelineConfig(
const std::string device_id, const PipelineConfig& pipeline_config,
const VolumeCurve& volume_curve) {
auto devices = GetDeviceInfos();
const auto dev = std::find_if(devices.begin(), devices.end(), [&device_id](auto candidate) {
return candidate.unique_id == device_id;
});
if (dev == devices.end()) {
return fit::make_error_promise(ZX_ERR_NOT_FOUND);
}
auto device = devices_[dev->token_id];
FX_DCHECK(device);
// UpdatePipelineConfig is only valid on a device that is currently routable; the routable state
// protects from devices being plugged or unplugged during update of the PipelineConfig,
// as well as ensures only one update to the PipelineConfig will be processed at a time.
if (!device->routable()) {
return fit::make_error_promise(ZX_ERR_BAD_STATE);
}
// UpdatePipelineConfig is only valid on a device without links (for the purpose of effects
// tuning). As such, the device is removed from route_graph to ensure all links are removed.
if (device->plugged()) {
route_graph_.RemoveDevice(device.get());
}
FX_DCHECK(link_matrix_.DestLinkCount(*device) == 0);
FX_DCHECK(link_matrix_.SourceLinkCount(*device) == 0);
device->UpdateRoutableState(false);
auto profile_params = DeviceConfig::OutputDeviceProfile::Parameters{
.pipeline_config = pipeline_config, .volume_curve = volume_curve};
return device->UpdateDeviceProfile(profile_params).and_then([this, device]() {
device->UpdateRoutableState(true);
if (device->plugged()) {
route_graph_.AddDevice(device.get());
}
});
}
void AudioDeviceManager::AddDevice(const std::shared_ptr<AudioDevice>& device) {
TRACE_DURATION("audio", "AudioDeviceManager::AddDevice");
FX_DCHECK(device != nullptr);
threading_model_.FidlDomain().executor()->schedule_task(
device->Startup()
.and_then([this, device]() mutable {
devices_pending_init_.insert({device->token(), std::move(device)});
})
.or_else([device](zx_status_t& error) {
FX_PLOGS(ERROR, error) << "AddDevice failed";
Reporter::Singleton().FailedToStartDevice(device->name());
device->Shutdown();
}));
}
void AudioDeviceManager::ActivateDevice(const std::shared_ptr<AudioDevice>& device) {
TRACE_DURATION("audio", "AudioDeviceManager::ActivateDevice");
FX_DCHECK(device != nullptr);
// Have we already been removed from the pending list? If so, the device is
// already shutting down and there is nothing to be done.
if (devices_pending_init_.find(device->token()) == devices_pending_init_.end()) {
return;
}
// If this device is still waiting for initialization, move it over to the set of active devices.
// Otherwise (if not waiting for initialization), we've been removed.
auto dev = devices_pending_init_.extract(device->token());
if (!dev) {
return;
}
devices_.insert(std::move(dev));
device->SetActivated();
// Apply persisted effects updates.
std::vector<fit::promise<void, void>> promises;
for (auto it : persisted_effects_updates_) {
std::string instance_name = it.first;
std::string config = it.second;
promises.push_back(
device->UpdateEffect(instance_name, config)
.then([instance_name,
config](fit::result<void, fuchsia::media::audio::UpdateEffectError>& result) {
if (result.is_error()) {
FX_LOGS_FIRST_N(ERROR, 10) << "Unable to update effect " << instance_name
<< ", error code " << static_cast<int>(result.error());
}
}));
}
threading_model_.FidlDomain().executor()->schedule_task(
fit::join_promise_vector(std::move(promises)));
// Notify interested users of the new device.
auto info = device->GetDeviceInfo();
// We always report is_default as false in the OnDeviceAdded event. There will be a following
// DefaultDeviceChange event that will signal if this device is now the default.
info.is_default = false;
for (auto& client : bindings_.bindings()) {
client->events().OnDeviceAdded(info);
}
if (device->plugged()) {
OnDevicePlugged(device, device->plug_time());
}
}
void AudioDeviceManager::RemoveDevice(const std::shared_ptr<AudioDevice>& device) {
TRACE_DURATION("audio", "AudioDeviceManager::RemoveDevice");
FX_DCHECK(device != nullptr);
FX_LOGS(INFO) << "Removing " << (device->is_input() ? "input" : "output") << " '"
<< device->name() << "'";
// If device was active: reset the default (based on most-recently-plugged).
OnPlugStateChanged(device, false, device->plug_time());
device->Shutdown();
auto& device_set = device->activated() ? devices_ : devices_pending_init_;
device_set.erase(device->token());
// If device was active: notify clients of the removal.
if (device->activated()) {
for (auto& client : bindings_.bindings()) {
client->events().OnDeviceRemoved(device->token());
}
}
}
void AudioDeviceManager::OnPlugStateChanged(const std::shared_ptr<AudioDevice>& device,
bool plugged, zx::time plug_time) {
TRACE_DURATION("audio", "AudioDeviceManager::OnPlugStateChanged");
FX_DCHECK(device != nullptr);
// Update our bookkeeping for device's plug state. If no change, we're done.
if (!device->UpdatePlugState(plugged, plug_time)) {
return;
}
// If the device is not yet activated, we should not be changing routes.
bool activated = devices_.find(device->token()) != devices_.end();
if (!activated) {
return;
}
if (plugged) {
OnDevicePlugged(device, plug_time);
} else {
OnDeviceUnplugged(device, plug_time);
}
}
std::vector<fuchsia::media::AudioDeviceInfo> AudioDeviceManager::GetDeviceInfos() {
TRACE_DURATION("audio", "AudioDeviceManager::GetDevices");
std::vector<fuchsia::media::AudioDeviceInfo> ret;
for (const auto& [_, dev] : devices_) {
if (dev->token() != ZX_KOID_INVALID) {
auto info = dev->GetDeviceInfo();
info.is_default =
(dev->token() == (dev->is_input() ? default_input_token_ : default_output_token_));
ret.push_back(std::move(info));
}
}
return ret;
}
void AudioDeviceManager::GetDevices(GetDevicesCallback cbk) { cbk(GetDeviceInfos()); }
void AudioDeviceManager::GetDeviceGain(uint64_t device_token, GetDeviceGainCallback cbk) {
TRACE_DURATION("audio", "AudioDeviceManager::GetDeviceGain");
auto it = devices_.find(device_token);
if (it == devices_.end()) {
cbk(ZX_KOID_INVALID, {});
return;
}
auto [_, dev] = *it;
FX_DCHECK(dev->device_settings() != nullptr);
auto info = dev->device_settings()->GetGainInfo();
cbk(device_token, info);
}
void AudioDeviceManager::SetDeviceGain(uint64_t device_token,
fuchsia::media::AudioGainInfo gain_info,
fuchsia::media::AudioGainValidFlags set_flags) {
TRACE_DURATION("audio", "AudioDeviceManager::SetDeviceGain");
auto it = devices_.find(device_token);
if (it == devices_.end()) {
return;
}
auto [_, dev] = *it;
// SetGainInfo clamps out-of-range values (e.g. +infinity) into the device-
// allowed gain range. NAN is undefined (signless); handle it here and exit.
if (((set_flags & fuchsia::media::AudioGainValidFlags::GAIN_VALID) ==
fuchsia::media::AudioGainValidFlags::GAIN_VALID) &&
isnan(gain_info.gain_db)) {
FX_LOGS(WARNING) << "Invalid device gain " << gain_info.gain_db << " dB -- making no change";
return;
}
dev->system_gain_dirty = true;
// Change the gain and then report the new settings to our clients.
dev->SetGainInfo(gain_info, set_flags);
NotifyDeviceGainChanged(*dev);
}
void AudioDeviceManager::GetDefaultInputDevice(GetDefaultInputDeviceCallback cbk) {
cbk(default_input_token_);
}
void AudioDeviceManager::GetDefaultOutputDevice(GetDefaultOutputDeviceCallback cbk) {
cbk(default_output_token_);
}
std::shared_ptr<AudioDevice> AudioDeviceManager::FindLastPlugged(AudioObject::Type type,
bool allow_unplugged) {
TRACE_DURATION("audio", "AudioDeviceManager::FindLastPlugged");
FX_DCHECK((type == AudioObject::Type::Output) || (type == AudioObject::Type::Input));
std::shared_ptr<AudioDevice> best = nullptr;
// TODO(johngro): Consider tracking last-plugged times in a fbl::WAVLTree, so
// this operation becomes O(1). N is pretty low right now, so the benefits do
// not currently outweigh the complexity of maintaining this index.
for (auto& [_, device] : devices_) {
if (device->type() != type) {
continue;
}
if ((best == nullptr) || (!best->plugged() && device->plugged()) ||
((best->plugged() == device->plugged()) && (best->plug_time() < device->plug_time()))) {
best = device;
}
}
FX_DCHECK((best == nullptr) || (best->type() == type));
if (!allow_unplugged && best && !best->plugged()) {
return nullptr;
}
return best;
}
void AudioDeviceManager::OnDeviceUnplugged(const std::shared_ptr<AudioDevice>& device,
zx::time plug_time) {
TRACE_DURATION("audio", "AudioDeviceManager::OnDeviceUnplugged");
FX_DCHECK(device);
FX_LOGS(INFO) << "Unplugged " << (device->is_input() ? "input" : "output") << " '"
<< device->name() << "'";
device->UpdatePlugState(/*plugged=*/false, plug_time);
if (device->routable()) {
route_graph_.RemoveDevice(device.get());
}
UpdateDefaultDevice(device->is_input());
}
void AudioDeviceManager::OnDevicePlugged(const std::shared_ptr<AudioDevice>& device,
zx::time plug_time) {
TRACE_DURATION("audio", "AudioDeviceManager::OnDevicePlugged");
FX_DCHECK(device);
FX_LOGS(INFO) << "Plugged " << (device->is_input() ? "input" : "output") << " '" << device->name()
<< "'";
device->UpdatePlugState(/*plugged=*/true, plug_time);
if (device->routable()) {
route_graph_.AddDevice(device.get());
}
UpdateDefaultDevice(device->is_input());
}
void AudioDeviceManager::NotifyDeviceGainChanged(const AudioDevice& device) {
TRACE_DURATION("audio", "AudioDeviceManager::NotifyDeviceGainChanged");
FX_DCHECK(device.device_settings() != nullptr);
auto info = device.device_settings()->GetGainInfo();
for (auto& client : bindings_.bindings()) {
client->events().OnDeviceGainChanged(device.token(), info);
}
}
void AudioDeviceManager::UpdateDefaultDevice(bool input) {
TRACE_DURATION("audio", "AudioDeviceManager::UpdateDefaultDevice");
const auto new_dev =
FindLastPlugged(input ? AudioObject::Type::Input : AudioObject::Type::Output);
uint64_t new_id = new_dev ? new_dev->token() : ZX_KOID_INVALID;
uint64_t& old_id = input ? default_input_token_ : default_output_token_;
if (old_id != new_id) {
FX_LOGS(INFO) << "Default " << (input ? "input" : "output") << " '"
<< (new_dev ? new_dev->name() : "none") << "'";
for (auto& client : bindings_.bindings()) {
client->events().OnDefaultDeviceChanged(old_id, new_id);
}
old_id = new_id;
}
}
void AudioDeviceManager::AddDeviceByVersion(zx::channel device_channel, std::string device_name,
bool is_input, AudioDriverVersion version) {
FX_LOGS(DEBUG) << "AddingByVersion (" << (version == AudioDriverVersion::V1 ? "V1" : "V2") << ")"
<< (is_input ? "input" : "output") << " '" << device_name << "'";
switch (version) {
case AudioDriverVersion::V1:
AddDeviceByChannel(std::move(device_channel), std::move(device_name), is_input);
break;
case AudioDriverVersion::V2:
fidl::InterfaceHandle<fuchsia::hardware::audio::StreamConfig> stream_config = {};
stream_config.set_channel(std::move(device_channel));
AddDeviceByChannel2(std::move(device_name), is_input, std::move(stream_config));
break;
}
}
void AudioDeviceManager::AddDeviceByChannel(zx::channel device_channel, std::string device_name,
bool is_input) {
TRACE_DURATION("audio", "AudioDeviceManager::AddDeviceByChannel");
FX_LOGS(INFO) << "Adding " << (is_input ? "input" : "output") << " '" << device_name << "'";
// Hand the stream off to the proper type of class to manage.
std::shared_ptr<AudioDevice> new_device;
if (is_input) {
new_device = AudioInput::Create(device_name, std::move(device_channel), &threading_model(),
this, &link_matrix_);
} else {
new_device = std::make_shared<DriverOutput>(device_name, &threading_model(), this,
std::move(device_channel), &link_matrix_,
process_config_.default_volume_curve());
}
if (new_device == nullptr) {
FX_LOGS(ERROR) << "Failed to instantiate audio " << (is_input ? "input" : "output") << " for '"
<< device_name << "'";
}
AddDevice(std::move(new_device));
}
void AudioDeviceManager::AddDeviceByChannel2(
std::string device_name, bool is_input,
fidl::InterfaceHandle<fuchsia::hardware::audio::StreamConfig> stream_config) {
TRACE_DURATION("audio", "AudioDeviceManager::AddDeviceByChannel2");
FX_LOGS(INFO) << "Adding " << (is_input ? "input" : "output") << " '" << device_name << "'";
// Hand the stream off to the proper type of class to manage.
std::shared_ptr<AudioDevice> new_device;
if (is_input) {
new_device = AudioInput::Create(device_name, std::move(stream_config), &threading_model(), this,
&link_matrix_);
} else {
new_device = std::make_shared<DriverOutput>(device_name, &threading_model(), this,
std::move(stream_config), &link_matrix_,
process_config_.default_volume_curve());
}
if (new_device == nullptr) {
FX_LOGS(ERROR) << "Failed to instantiate audio " << (is_input ? "input" : "output") << " for '"
<< device_name << "'";
}
AddDevice(std::move(new_device));
}
} // namespace media::audio