blob: 0a55749a9ecebd48a97e690ac69ad90c19ea2444 [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 <fuchsia/media/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/fit/function.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/ref_ptr.h>
#include "src/media/audio/audio_core/audio_device.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/audio_plug_detector.h"
#include "src/media/audio/audio_core/audio_renderer_impl.h"
#include "src/media/audio/audio_core/fwd_decls.h"
#include "src/media/audio/lib/effects_loader/effects_loader.h"
namespace media::audio {
class AudioCapturerImpl;
class AudioDeviceManager : public fuchsia::media::AudioDeviceEnumerator {
explicit AudioDeviceManager(AudioCoreImpl* service);
// Initialize the input/output manager. Called once from service impl, at startup time. Should...
// 1) Initialize the mixing thread pool.
// 2) Instantiate all of the built-in audio output devices.
// 3) Monitor for plug/unplug events for pluggable audio output devices.
// 4) Load the device effects library.
zx_status_t Init();
// Blocking call. Called by the service, once, when it is time to shutdown the service
// implementation. While this function is blocking, it must never block for long. Our process is
// going away; this is our last chance to perform a clean shutdown. If an unclean shutdown must
// be performed in order to implode in a timely fashion, so be it.
// Shutdown must be idempotent and safe to call from this object's destructor (although this
// should never be necessary). If a shutdown called from this destructor must do real work,
// something has gone Very Seriously Wrong.
void Shutdown();
// Add a new device-enumerator client. Called from service framework when a new client connects.
void AddDeviceEnumeratorClient(
fidl::InterfaceRequest<fuchsia::media::AudioDeviceEnumerator> request);
// Add an AudioRenderer to the set of active AudioRenderers.
void AddAudioRenderer(fbl::RefPtr<AudioRendererImpl> audio_renderer) {
// Remove an AudioRenderer from the set of active AudioRenderers.
void RemoveAudioRenderer(AudioRendererImpl* audio_renderer) {
FXL_DCHECK(audio_renderer != nullptr);
// Select the initial set of outputs for a newly-configured AudioRenderer.
void SelectOutputsForAudioRenderer(AudioRendererImpl* audio_renderer);
// Link an output to an AudioRenderer.
void LinkOutputToAudioRenderer(AudioOutput* output, AudioRendererImpl* audio_renderer);
// Add/remove an AudioCapturer to/from the set of active AudioCapturers.
void AddAudioCapturer(const fbl::RefPtr<AudioCapturerImpl>& audio_capturer);
void RemoveAudioCapturer(AudioCapturerImpl* audio_capturer);
// Schedule a closure to run on our encapsulating service's main message loop.
void ScheduleMainThreadTask(fit::closure task);
// Begin initializing a device and add it to the set of devices waiting to be initialized.
// Called from the plug detector when a new stream device first shows up.
zx_status_t AddDevice(const fbl::RefPtr<AudioDevice>& device);
// Move device from pending-init list to active-devices list. Notify users and re-evaluate policy.
void ActivateDevice(const fbl::RefPtr<AudioDevice>& device);
// Shutdown this device; remove it from the appropriate set of active devices.
void RemoveDevice(const fbl::RefPtr<AudioDevice>& device);
// Handles a plugged/unplugged state change for the supplied audio device.
void HandlePlugStateChange(const fbl::RefPtr<AudioDevice>& device, bool plugged,
zx_time_t plug_time);
void SetRoutingPolicy(fuchsia::media::AudioOutputRoutingPolicy policy);
static inline bool ValidateRoutingPolicy(fuchsia::media::AudioOutputRoutingPolicy policy) {
switch (policy) {
case fuchsia::media::AudioOutputRoutingPolicy::LAST_PLUGGED_OUTPUT:
case fuchsia::media::AudioOutputRoutingPolicy::ALL_PLUGGED_OUTPUTS:
return true;
// Note: no default: handler here. If someone adds a new policy to the enum without updating
// this code, we want a Build Break to notify us that we need to handle the new policy.
return false;
// SetSystemGain/Mute has been called. 'changed' tells us whether System Gain or Mute values
// actually changed. If not, only update devices that (because of calls to SetDeviceGain) have
// diverged from System settings.
void OnSystemGain(bool changed);
// Implementation of the AudioDeviceEnumerator FIDL interface.
void GetDevices(GetDevicesCallback cbk) final;
void GetDeviceGain(uint64_t device_token, GetDeviceGainCallback cbk) final;
void SetDeviceGain(uint64_t device_token, fuchsia::media::AudioGainInfo gain_info,
uint32_t set_flags) final;
void GetDefaultInputDevice(GetDefaultInputDeviceCallback cbk) final;
void GetDefaultOutputDevice(GetDefaultOutputDeviceCallback cbk) final;
void AddDeviceByChannel(::zx::channel device_channel, std::string device_name,
bool is_input) final;
// KeyTraits we use to sort our AudioDeviceSettings set to ensure uniqueness.
struct AudioDeviceSettingsKeyTraits {
static const AudioDeviceSettings* GetKey(const AudioDeviceSettings& obj) { return &obj; }
static bool LessThan(const AudioDeviceSettings* k1, const AudioDeviceSettings* k2) {
return (k1->is_input() && !k2->is_input()) ||
((k1->is_input() == k2->is_input()) &&
(memcmp(&k1->uid(), &k2->uid(), sizeof(k1->uid())) < 0));
static bool EqualTo(const AudioDeviceSettings* k1, const AudioDeviceSettings* k2) {
return (k1->is_input() == k2->is_input()) &&
(memcmp(&k1->uid(), &k2->uid(), sizeof(k1->uid())) == 0);
using DeviceSettingsSet =
::fbl::WAVLTree<const AudioDeviceSettings*, fbl::RefPtr<AudioDeviceSettings>,
// Find the most-recently plugged device (per type: input or output) excluding throttle_output. If
// allow_unplugged, return the most-recently UNplugged device if no plugged devices are found --
// otherwise return nullptr.
fbl::RefPtr<AudioDevice> FindLastPlugged(AudioObject::Type type, bool allow_unplugged = false);
fbl::RefPtr<AudioOutput> FindLastPluggedOutput(bool allow_unplugged = false) {
auto dev = FindLastPlugged(AudioObject::Type::Output, allow_unplugged);
FXL_DCHECK(!dev || (dev->type() == AudioObject::Type::Output));
return fbl::RefPtr<AudioOutput>::Downcast(std::move(dev));
fbl::RefPtr<AudioInput> FindLastPluggedInput(bool allow_unplugged = false) {
auto dev = FindLastPlugged(AudioObject::Type::Input, allow_unplugged);
FXL_DCHECK(!dev || (dev->type() == AudioObject::Type::Input));
return fbl::RefPtr<AudioInput>::Downcast(std::move(dev));
// Methods to handle routing policy -- when an existing device is unplugged or completely removed,
// or when a new device is plugged or added to the system.
void OnDeviceUnplugged(const fbl::RefPtr<AudioDevice>& device, zx_time_t plug_time);
void OnDevicePlugged(const fbl::RefPtr<AudioDevice>& device, zx_time_t plug_time);
void LinkToAudioCapturers(const fbl::RefPtr<AudioDevice>& device);
// Commit any pending device-settings changes to disk (if settings are disk-backed), then remove
// the settings from our persisted_device_settings_ map.
void FinalizeDeviceSettings(const AudioDevice& device);
// Send notification to users that this device's gain settings have changed.
void NotifyDeviceGainChanged(const AudioDevice& device);
// Re-evaluate which device is the default. Notify users, if this has changed.
void UpdateDefaultDevice(bool input);
// Update a device gain to the "system" gain exposed by the top-level service.
// TODO(johngro): Remove this when we remove system gain entirely.
void UpdateDeviceToSystemGain(const fbl::RefPtr<AudioDevice>& device);
// Commit any dirty settings to storage, (re)scheduling the timer as needed.
void CommitDirtySettings();
void CommitDirtySettingsThunk(async_dispatcher_t*, async::TaskBase*, zx_status_t) {
// Pointer to the service which encapsulates us. This pointer cannot be bad while we still exist.
AudioCoreImpl* service_;
// The set of AudioDeviceEnumerator clients we are currently tending to.
fidl::BindingSet<fuchsia::media::AudioDeviceEnumerator> bindings_;
// Our sets of currently active audio devices, AudioCapturers, and AudioRenderers.
// These must only be manipulated on main message loop thread. No synchronization should be needed
fbl::WAVLTree<uint64_t, fbl::RefPtr<AudioDevice>> devices_pending_init_;
fbl::WAVLTree<uint64_t, fbl::RefPtr<AudioDevice>> devices_;
fbl::DoublyLinkedList<fbl::RefPtr<AudioCapturerImpl>> audio_capturers_;
fbl::DoublyLinkedList<fbl::RefPtr<AudioRendererImpl>> audio_renderers_;
// The special throttle output always exists and is used by every renderer.
fbl::RefPtr<AudioOutput> throttle_output_;
// A helper class we will use to detect plug/unplug events for audio devices
AudioPlugDetector plug_detector_;
// State which affects routing policy.
fuchsia::media::AudioOutputRoutingPolicy routing_policy_ =
uint64_t default_output_token_ = ZX_KOID_INVALID;
uint64_t default_input_token_ = ZX_KOID_INVALID;
// The unique AudioDeviceSettings subset we track that needs disk-persistence.
DeviceSettingsSet persisted_device_settings_;
async::TaskMethod<AudioDeviceManager, &AudioDeviceManager::CommitDirtySettingsThunk>
EffectsLoader effects_loader_;
} // namespace media::audio