blob: c3d2dae98ba5ecb43b96001257fbc981d2f27c73 [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 <string>
#include "src/media/audio/audio_core/audio_capturer_impl.h"
#include "src/media/audio/audio_core/audio_core_impl.h"
#include "src/media/audio/audio_core/audio_link.h"
#include "src/media/audio/audio_core/audio_plug_detector.h"
#include "src/media/audio/audio_core/audio_renderer_impl.h"
#include "src/media/audio/audio_core/driver_output.h"
#include "src/media/audio/audio_core/reporter.h"
#include "src/media/audio/audio_core/throttle_output.h"
#include "src/media/audio/lib/logging/logging.h"
namespace media::audio {
AudioDeviceManager::AudioDeviceManager(AudioCoreImpl* service) : service_(service) {}
AudioDeviceManager::~AudioDeviceManager() {
Shutdown();
FXL_DCHECK(devices_.is_empty());
}
// Configure this admin singleton object to manage audio device instances.
zx_status_t AudioDeviceManager::Init() {
// Give AudioDeviceSettings a chance to ensure its storage is happy.
AudioDeviceSettings::Initialize();
// Instantiate and initialize the default throttle output.
auto throttle_output = ThrottleOutput::Create(this);
if (throttle_output == nullptr) {
FXL_LOG(ERROR) << "AudioDeviceManager failed to create default throttle output!";
return ZX_ERR_NO_MEMORY;
}
zx_status_t res = throttle_output->Startup();
if (res != ZX_OK) {
FXL_PLOG(ERROR, res) << "AudioDeviceManager failed to initialize the throttle output";
throttle_output->Shutdown();
}
throttle_output_ = std::move(throttle_output);
// Start monitoring for plug/unplug events of pluggable audio output devices.
res = plug_detector_.Start(this);
if (res != ZX_OK) {
FXL_PLOG(ERROR, res) << "AudioDeviceManager failed to start plug detector";
return res;
}
// Initialize the EffectsLoader and load the device effect library, if present.
res = effects_loader_.LoadLibrary();
if (res == ZX_ERR_ALREADY_EXISTS) {
FXL_LOG(ERROR) << "EffectsLoader already started!";
} else if (res != ZX_OK) {
FXL_PLOG(WARNING, res) << "EffectsLoader::LoadLibrary failed";
}
return res;
}
// We are no longer managing audio devices, unwind everything.
void AudioDeviceManager::Shutdown() {
// Step #1: Stop monitoring plug/unplug events and cancel any pending settings
// commit task. We are shutting down and no longer care about these things.
plug_detector_.Stop();
commit_settings_task_.Cancel();
// Step #2: Shut down each active AudioCapturer in the system.
while (!audio_capturers_.is_empty()) {
auto audio_capturer = audio_capturers_.pop_front();
audio_capturer->Shutdown();
}
// Step #3: Shut down each active AudioRenderer in the system.
while (!audio_renderers_.is_empty()) {
auto audio_renderer = audio_renderers_.pop_front();
audio_renderer->Shutdown();
}
// Step #4: Shut down each device which is waiting for initialization.
while (!devices_pending_init_.is_empty()) {
auto device = devices_pending_init_.pop_front();
device->Shutdown();
}
// Step #5: Shut down each currently active device in the system.
while (!devices_.is_empty()) {
auto device = devices_.pop_front();
device->Shutdown();
FinalizeDeviceSettings(*device);
}
// Step #6: Shut down the throttle output.
throttle_output_->Shutdown();
throttle_output_ = nullptr;
}
void AudioDeviceManager::AddDeviceEnumeratorClient(
fidl::InterfaceRequest<fuchsia::media::AudioDeviceEnumerator> request) {
bindings_.AddBinding(this, std::move(request));
}
zx_status_t AudioDeviceManager::AddDevice(const fbl::RefPtr<AudioDevice>& device) {
FXL_DCHECK(device != nullptr);
FXL_DCHECK(device != throttle_output_);
FXL_DCHECK(!device->InContainer());
zx_status_t res = device->Startup();
if (res != ZX_OK) {
REP(DeviceStartupFailed(*device));
device->Shutdown();
} else {
devices_pending_init_.insert(std::move(device));
}
return res;
}
void AudioDeviceManager::ActivateDevice(const fbl::RefPtr<AudioDevice>& device) {
FXL_DCHECK(device != nullptr);
FXL_DCHECK(device != throttle_output_);
// 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 (!device->InContainer()) {
return;
}
// TODO(johngro): remove this when system gain is fully deprecated.
// For now, set each output "device" gain to the "system" gain value.
if (device->is_output()) {
UpdateDeviceToSystemGain(device);
}
// Determine whether this device's persistent settings are actually unique,
// or if they collide with another device's unique ID.
//
// If these settings are currently unique in the system, attempt to load the
// persisted settings from disk, or create a new persisted settings file for
// this device if the file is either absent or corrupt.
//
// If these settings are not unique, then copy the settings of the device we
// conflict with, and use them without persistence. Currently, when device
// instances conflict, we persist only the first instance's settings.
DeviceSettingsSet::iterator collision;
fbl::RefPtr<AudioDeviceSettings> settings = device->device_settings();
FXL_DCHECK(settings != nullptr);
if (persisted_device_settings_.insert_or_find(settings, &collision)) {
settings->InitFromDisk();
} else {
const uint8_t* id = settings->uid().data;
char id_buf[33];
std::snprintf(id_buf, sizeof(id_buf),
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", id[0], id[1],
id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12],
id[13], id[14], id[15]);
FXL_LOG(WARNING) << "Warning: Device ID (" << device->token()
<< ") shares a persistent unique ID (" << id_buf
<< ") with another device in the system. Initial Settings will be cloned "
"from this device, and not persisted";
settings->InitFromClone(*collision);
}
// Is this device configured to be ignored? If so, remove (don't activate) it.
if (settings->ignore_device()) {
REP(IgnoringDevice(*device));
RemoveDevice(device);
return;
}
REP(ActivatingDevice(*device));
// Move the device over to the set of active devices.
devices_.insert(devices_pending_init_.erase(*device));
device->SetActivated();
// TODO(mpuryear): Create this device instance's EffectsProcessor here?
// Now that we have our gain settings (restored from disk, cloned from
// others, or default), reapply them via the device itself. We do this in
// order to allow the device the chance to apply its own internal limits,
// which may not permit the values which had been read from disk.
//
// TODO(johngro): Clean this pattern up, it is really awkward. On the one
// hand, we would really like the settings to be completely independent from
// the devices, but on the other hand, there are limits for various settings
// which may be need imposed by the device's capabilities.
constexpr uint32_t kAllSetFlags = fuchsia::media::SetAudioGainFlag_GainValid |
fuchsia::media::SetAudioGainFlag_MuteValid |
fuchsia::media::SetAudioGainFlag_AgcValid;
fuchsia::media::AudioGainInfo gain_info;
settings->GetGainInfo(&gain_info);
REP(SettingDeviceGainInfo(*device, gain_info, kAllSetFlags));
device->SetGainInfo(gain_info, kAllSetFlags);
// TODO(mpuryear): Configure the EffectsProcessor based on settings, here?
// Notify interested users of this new device. Check whether this will become
// the new default device, so we can set 'is_default' in the notification
// properly. Right now, "default" device is defined simply as last-plugged.
fuchsia::media::AudioDeviceInfo info;
device->GetDeviceInfo(&info);
auto last_plugged = FindLastPlugged(device->type());
info.is_default = (last_plugged && (last_plugged->token() == device->token()));
for (auto& client : bindings_.bindings()) {
client->events().OnDeviceAdded(info);
}
// Reconsider our current routing policy now that a new device has arrived.
if (device->plugged()) {
zx_time_t plug_time = device->plug_time();
OnDevicePlugged(device, plug_time);
}
// Check whether the default device has changed; if so, update users.
UpdateDefaultDevice(device->is_input());
// Commit (or schedule a commit for) any dirty settings.
CommitDirtySettings();
}
void AudioDeviceManager::RemoveDevice(const fbl::RefPtr<AudioDevice>& device) {
FXL_DCHECK(device != nullptr);
FXL_DCHECK(device->is_output() || (device != throttle_output_));
REP(RemovingDevice(*device));
// TODO(mpuryear): Considering eliminating this; it may not be needed.
device->PreventNewLinks();
device->Unlink();
if (device->activated()) {
OnDeviceUnplugged(device, device->plug_time());
}
// TODO(mpuryear): Persist any final remaining device-effect settings?
device->Shutdown();
FinalizeDeviceSettings(*device);
// TODO(mpuryear): Delete this device instance's EffectsProcessor here?
if (device->InContainer()) {
auto& device_set = device->activated() ? devices_ : devices_pending_init_;
device_set.erase(*device);
// If device was active: reset the default & notify clients of the removal.
if (device->activated()) {
UpdateDefaultDevice(device->is_input());
for (auto& client : bindings_.bindings()) {
client->events().OnDeviceRemoved(device->token());
}
}
}
}
void AudioDeviceManager::HandlePlugStateChange(const fbl::RefPtr<AudioDevice>& device, bool plugged,
zx_time_t plug_time) {
FXL_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 (plugged) {
OnDevicePlugged(device, plug_time);
} else {
OnDeviceUnplugged(device, plug_time);
}
// Check whether the default device has changed; if so, update users.
UpdateDefaultDevice(device->is_input());
}
// SetSystemGain or SetSystemMute has been called. 'changed' tells us whether
// the System Gain / Mute values actually changed. If not, only update devices
// that (because of calls to SetDeviceGain) have diverged from System settings.
//
// We update link gains in Device::SetGainInfo rather than here, so that we
// catch changes to device gain coming from SetSystemGain OR SetDeviceGain.
void AudioDeviceManager::OnSystemGain(bool changed) {
for (auto& device : devices_) {
if (device.is_output() && (changed || device.system_gain_dirty)) {
UpdateDeviceToSystemGain(fbl::WrapRefPtr(&device));
NotifyDeviceGainChanged(device);
device.system_gain_dirty = false;
}
// We intentionally route System Gain only to Output devices, not Inputs.
// If needed, we could revisit this in the future.
}
}
void AudioDeviceManager::GetDevices(GetDevicesCallback cbk) {
std::vector<fuchsia::media::AudioDeviceInfo> ret;
for (const auto& dev : devices_) {
if (dev.token() != ZX_KOID_INVALID) {
fuchsia::media::AudioDeviceInfo info;
dev.GetDeviceInfo(&info);
info.is_default =
(dev.token() == (dev.is_input() ? default_input_token_ : default_output_token_));
ret.push_back(std::move(info));
}
}
cbk(std::move(ret));
}
void AudioDeviceManager::GetDeviceGain(uint64_t device_token, GetDeviceGainCallback cbk) {
auto dev = devices_.find(device_token);
fuchsia::media::AudioGainInfo info = {0};
if (dev.IsValid()) {
FXL_DCHECK(dev->device_settings() != nullptr);
dev->device_settings()->GetGainInfo(&info);
cbk(device_token, info);
} else {
cbk(ZX_KOID_INVALID, info);
}
}
void AudioDeviceManager::SetDeviceGain(uint64_t device_token,
fuchsia::media::AudioGainInfo gain_info,
uint32_t set_flags) {
auto dev = devices_.find(device_token);
if (!dev.IsValid()) {
return;
}
// 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::SetAudioGainFlag_GainValid) && isnan(gain_info.gain_db)) {
FXL_DLOG(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.
REP(SettingDeviceGainInfo(*dev, gain_info, set_flags));
dev->SetGainInfo(gain_info, set_flags);
NotifyDeviceGainChanged(*dev);
CommitDirtySettings();
}
void AudioDeviceManager::GetDefaultInputDevice(GetDefaultInputDeviceCallback cbk) {
cbk(default_input_token_);
}
void AudioDeviceManager::GetDefaultOutputDevice(GetDefaultOutputDeviceCallback cbk) {
cbk(default_output_token_);
}
void AudioDeviceManager::SelectOutputsForAudioRenderer(AudioRendererImpl* audio_renderer) {
FXL_DCHECK(audio_renderer);
FXL_DCHECK(audio_renderer->format_info_valid());
FXL_DCHECK(ValidateRoutingPolicy(routing_policy_));
// TODO(johngro): Add a way to assert that we are on the message loop thread.
// Regardless of policy, link the special throttle output to every renderer.
LinkOutputToAudioRenderer(throttle_output_.get(), audio_renderer);
switch (routing_policy_) {
case fuchsia::media::AudioOutputRoutingPolicy::ALL_PLUGGED_OUTPUTS: {
for (auto& device : devices_) {
FXL_DCHECK(device.is_input() || device.is_output());
if (device.is_output() && device.plugged()) {
LinkOutputToAudioRenderer(static_cast<AudioOutput*>(&device), audio_renderer);
}
}
} break;
case fuchsia::media::AudioOutputRoutingPolicy::LAST_PLUGGED_OUTPUT: {
fbl::RefPtr<AudioOutput> last_plugged = FindLastPluggedOutput();
if (last_plugged != nullptr) {
LinkOutputToAudioRenderer(last_plugged.get(), audio_renderer);
}
} break;
}
// Figure out the initial minimum clock lead time requirement.
audio_renderer->RecomputeMinClockLeadTime();
}
void AudioDeviceManager::LinkOutputToAudioRenderer(AudioOutput* output,
AudioRendererImpl* audio_renderer) {
FXL_DCHECK(output);
FXL_DCHECK(audio_renderer);
// Do not create any links if AudioRenderer's output format is not yet set.
// Links will be created during SelectOutputsForAudioRenderer when the
// AudioRenderer format is finally set via AudioRendererImpl::SetStreamType.
if (!audio_renderer->format_info_valid())
return;
fbl::RefPtr<AudioLink> link =
AudioObject::LinkObjects(fbl::WrapRefPtr(audio_renderer), fbl::WrapRefPtr(output));
// TODO(johngro): get rid of the throttle output. See MTWN-52
if ((link != nullptr) && (output == throttle_output_.get())) {
FXL_DCHECK(link->source_type() == AudioLink::SourceType::Packet);
audio_renderer->SetThrottleOutput(
fbl::RefPtr<AudioLinkPacketSource>::Downcast(std::move(link)));
}
}
void AudioDeviceManager::AddAudioCapturer(const fbl::RefPtr<AudioCapturerImpl>& audio_capturer) {
FXL_DCHECK(audio_capturer != nullptr);
FXL_DCHECK(!audio_capturer->InContainer());
audio_capturers_.push_back(audio_capturer);
fbl::RefPtr<AudioDevice> source;
if (audio_capturer->loopback()) {
source = FindLastPluggedOutput(true);
} else {
source = FindLastPluggedInput(true);
}
if (source != nullptr) {
FXL_DCHECK(source->driver() != nullptr);
auto initial_format = source->driver()->GetSourceFormat();
if (initial_format) {
audio_capturer->SetInitialFormat(*initial_format);
}
if (source->plugged()) {
AudioObject::LinkObjects(std::move(source), std::move(audio_capturer));
}
}
}
void AudioDeviceManager::RemoveAudioCapturer(AudioCapturerImpl* audio_capturer) {
FXL_DCHECK(audio_capturer != nullptr);
FXL_DCHECK(audio_capturer->InContainer());
audio_capturers_.erase(*audio_capturer);
}
void AudioDeviceManager::ScheduleMainThreadTask(fit::closure task) {
FXL_DCHECK(service_);
service_->ScheduleMainThreadTask(std::move(task));
}
fbl::RefPtr<AudioDevice> AudioDeviceManager::FindLastPlugged(AudioObject::Type type,
bool allow_unplugged) {
FXL_DCHECK((type == AudioObject::Type::Output) || (type == AudioObject::Type::Input));
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& obj : devices_) {
auto& device = static_cast<AudioDevice&>(obj);
if ((device.type() != type) || device.device_settings()->disallow_auto_routing()) {
continue;
}
if ((best == nullptr) || (!best->plugged() && device.plugged()) ||
((best->plugged() == device.plugged()) && (best->plug_time() < device.plug_time()))) {
best = &device;
}
}
FXL_DCHECK((best == nullptr) || (best->type() == type));
if (!allow_unplugged && best && !best->plugged())
return nullptr;
return fbl::WrapRefPtr(best);
}
// Our policy governing the routing of audio outputs has changed. For the output
// considered "preferred" (because it was most-recently-added), nothing changes;
// all other outputs will toggle on or off, depending on the policy chosen.
void AudioDeviceManager::SetRoutingPolicy(fuchsia::media::AudioOutputRoutingPolicy routing_policy) {
if (!ValidateRoutingPolicy(routing_policy)) {
FXL_LOG(ERROR) << "Out-of-range RoutingPolicy(" << fidl::ToUnderlying(routing_policy) << ")";
// TODO(mpuryear): Once AudioCore has a way to know which connection made
// this request, terminate that connection now rather than doing nothing.
return;
}
if (routing_policy == routing_policy_) {
return;
}
routing_policy_ = routing_policy;
fbl::RefPtr<AudioOutput> last_plugged_output = FindLastPluggedOutput();
// Iterate thru all of our audio devices -- only a subset are affected.
for (auto& dev_obj : devices_) {
// Input devices are unaffected by changes in output-routing.
if (dev_obj.is_input()) {
continue;
}
// Only plugged-in (output) devices are affected by output-routing.
auto& output = static_cast<AudioOutput&>(dev_obj);
if (!output.plugged()) {
continue;
}
// If device is most-recently plugged, it is unaffected by this policy
// change. Either way, it will continue to be attached to every renderer.
FXL_DCHECK(&output != throttle_output_.get());
if (&output == last_plugged_output.get()) {
continue;
}
// We've excluded inputs, unplugged outputs and the most-recently-plugged
// output. For each remaining output (based on the new policy), we ...
if (routing_policy == fuchsia::media::AudioOutputRoutingPolicy::LAST_PLUGGED_OUTPUT) {
// ...disconnect it (i.e. link each AudioRenderer to Last-Plugged only),
// or...
dev_obj.UnlinkSources();
} else {
// ...attach it (i.e. link each AudioRenderer to all output devices).
for (auto& obj : audio_renderers_) {
FXL_DCHECK(obj.is_audio_renderer());
auto& audio_renderer = static_cast<AudioRendererImpl&>(obj);
LinkOutputToAudioRenderer(&output, &audio_renderer);
}
}
}
// After a route change, recalculate minimum clock lead time requirements.
for (auto& obj : audio_renderers_) {
FXL_DCHECK(obj.is_audio_renderer());
auto& audio_renderer = static_cast<AudioRendererImpl&>(obj);
audio_renderer.RecomputeMinClockLeadTime();
}
}
void AudioDeviceManager::OnDeviceUnplugged(const fbl::RefPtr<AudioDevice>& device,
zx_time_t plug_time) {
FXL_DCHECK(device);
FXL_DCHECK(ValidateRoutingPolicy(routing_policy_));
// First, see if the device is last-plugged (before updating its plug state).
bool was_last_plugged = FindLastPlugged(device->type()) == device;
// Update the device's plug state. If no change, then we are done.
if (!device->UpdatePlugState(false, plug_time)) {
return;
}
// This device is newly-unplugged. Unlink all its current connections.
device->Unlink();
// If the device which was unplugged was not the last plugged device in the
// system, then there has been no change in who was the last plugged device,
// and no updates to the routing state are needed.
if (was_last_plugged) {
if (device->is_output()) {
// This was an output. If applying 'last plugged output' policy, link each
// AudioRenderer to the most-recently-plugged output (if any). Then do the
// same for each 'loopback' AudioCapturer. Note: our current (hack)
// routing policy for inputs is always 'last plugged'.
FXL_DCHECK(static_cast<AudioOutput*>(device.get()) != throttle_output_.get());
fbl::RefPtr<AudioOutput> replacement = FindLastPluggedOutput();
if (replacement) {
if (routing_policy_ == fuchsia::media::AudioOutputRoutingPolicy::LAST_PLUGGED_OUTPUT) {
for (auto& audio_renderer : audio_renderers_) {
LinkOutputToAudioRenderer(replacement.get(), &audio_renderer);
}
}
LinkToAudioCapturers(std::move(replacement));
}
} else {
// Removed device was the most-recently-plugged input device. Determine
// the new most-recently-plugged input (if any remain), and iterate our
// AudioCapturer list to link each non-loopback AudioCapturer to the new
// default.
FXL_DCHECK(device->is_input());
fbl::RefPtr<AudioInput> replacement = FindLastPluggedInput();
if (replacement) {
LinkToAudioCapturers(std::move(replacement));
}
}
}
// If removed device was an output, recompute the renderer minimum lead time.
if (device->is_output()) {
for (auto& audio_renderer : audio_renderers_) {
audio_renderer.RecomputeMinClockLeadTime();
}
}
}
void AudioDeviceManager::OnDevicePlugged(const fbl::RefPtr<AudioDevice>& device,
zx_time_t plug_time) {
FXL_DCHECK(device);
if (device->is_output()) {
// This new device is an output. Inspect the renderer list and "do the right
// thing" based on our routing policy. If last-plugged policy, change each
// renderer to target this device (assuming it IS most-recently-plugged).
// If all-plugged policy, just add this output to the list.
//
// Then, apply last-plugged policy to all capturers with loopback sources.
// The policy mentioned above currently only pertains to Output Routing.
fbl::RefPtr<AudioOutput> last_plugged = FindLastPluggedOutput();
auto output = fbl::RefPtr<AudioOutput>::Downcast(std::move(device));
FXL_DCHECK(ValidateRoutingPolicy(routing_policy_));
bool lp_policy =
(routing_policy_ == fuchsia::media::AudioOutputRoutingPolicy::LAST_PLUGGED_OUTPUT);
bool is_lp = (output == last_plugged);
if (is_lp && lp_policy) {
for (auto& unlink_tgt : devices_) {
if (unlink_tgt.is_output() && (&unlink_tgt != output.get())) {
unlink_tgt.UnlinkSources();
}
}
}
if (is_lp || !lp_policy) {
for (auto& audio_renderer : audio_renderers_) {
LinkOutputToAudioRenderer(output.get(), &audio_renderer);
// If we are adding a new link (regardless of whether we may or may
// not have removed old links based on the specific active policy)
// because of an output becoming plugged in, we need to recompute the
// minimum clock lead time requirement, and perhaps update users as to
// what it is supposed to be.
//
// TODO(johngro) : In theory, this could be optimized. We don't
// *technically* need to go over the entire set of links and find the
// largest minimum lead time requirement if we know (for example) that
// we just added a link, but didn't remove any. Right now, we are
// sticking to the simple approach because we know that N (the total
// number of outputs an input is linked to) is small, and maintaining
// optimized/specialized logic for computing this value would start to
// become a real pain as we start to get more complicated in our
// approach to policy based routing.
audio_renderer.RecomputeMinClockLeadTime();
}
}
// 'loopback' AudioCapturers should listen to this output now
if (is_lp) {
LinkToAudioCapturers(std::move(output));
}
} else {
FXL_DCHECK(device->is_input());
fbl::RefPtr<AudioInput> last_plugged = FindLastPluggedInput();
auto& input = static_cast<AudioInput&>(*device);
// non-'loopback' AudioCapturers should listen to this input now
if (&input == last_plugged.get()) {
LinkToAudioCapturers(std::move(device));
}
}
}
// New device arrived and is the most-recently-plugged.
// * If device is an output, all 'loopback' AudioCapturers should listen to this
// output going forward (it is the default output).
// * If device is an input, then all NON-'loopback' AudioCapturers should listen
// to this input going forward (it is the default input).
void AudioDeviceManager::LinkToAudioCapturers(const fbl::RefPtr<AudioDevice>& device) {
bool link_to_loopbacks = device->is_output();
for (auto& audio_capturer : audio_capturers_) {
if (audio_capturer.loopback() == link_to_loopbacks) {
audio_capturer.UnlinkSources();
AudioObject::LinkObjects(std::move(device), fbl::WrapRefPtr(&audio_capturer));
}
}
}
void AudioDeviceManager::FinalizeDeviceSettings(const AudioDevice& device) {
const auto& settings = device.device_settings();
if ((settings == nullptr) || !settings->InContainer()) {
return;
}
settings->Commit(true);
persisted_device_settings_.erase(*settings);
}
void AudioDeviceManager::NotifyDeviceGainChanged(const AudioDevice& device) {
fuchsia::media::AudioGainInfo info;
FXL_DCHECK(device.device_settings() != nullptr);
device.device_settings()->GetGainInfo(&info);
for (auto& client : bindings_.bindings()) {
client->events().OnDeviceGainChanged(device.token(), info);
}
}
void AudioDeviceManager::UpdateDefaultDevice(bool input) {
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) {
for (auto& client : bindings_.bindings()) {
client->events().OnDefaultDeviceChanged(old_id, new_id);
}
old_id = new_id;
}
}
void AudioDeviceManager::UpdateDeviceToSystemGain(const fbl::RefPtr<AudioDevice>& device) {
constexpr uint32_t set_flags =
fuchsia::media::SetAudioGainFlag_GainValid | fuchsia::media::SetAudioGainFlag_MuteValid;
fuchsia::media::AudioGainInfo set_cmd = {
service_->system_gain_db(),
service_->system_muted() ? fuchsia::media::AudioGainInfoFlag_Mute : 0u};
FXL_DCHECK(device != nullptr);
REP(SettingDeviceGainInfo(*device, set_cmd, set_flags));
device->SetGainInfo(set_cmd, set_flags);
CommitDirtySettings();
}
void AudioDeviceManager::CommitDirtySettings() {
zx::time next = zx::time::infinite();
for (auto& settings : persisted_device_settings_) {
zx::time tmp = settings.Commit();
if (tmp < next) {
next = tmp;
}
}
// If our commit task is waiting to fire, try to cancel it.
if (commit_settings_task_.is_pending()) {
commit_settings_task_.Cancel();
}
// If we need to update in the future, schedule a commit task to do so.
if (next != zx::time::infinite()) {
commit_settings_task_.PostForTime(service_->dispatcher(), next);
}
}
void AudioDeviceManager::AddDeviceByChannel(::zx::channel device_channel, std::string device_name,
bool is_input) {
AUD_VLOG(TRACE) << " adding " << (is_input ? "input" : "output") << " '" << device_name << "'";
// 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(device_channel), this);
} else {
new_device = DriverOutput::Create(std::move(device_channel), this);
}
if (new_device == nullptr) {
FXL_LOG(ERROR) << "Failed to instantiate audio " << (is_input ? "input" : "output") << " for '"
<< device_name << "'";
}
REP(AddingDevice(device_name, *new_device));
zx_status_t status = AddDevice(std::move(new_device));
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "AddDevice failed";
}
}
} // namespace media::audio