From 0223ac3e092249c792bedcab2bf5dbaec443f960 Mon Sep 17 00:00:00 2001 From: Tanu Kaskinen Date: Wed, 21 May 2014 11:51:27 +0300 Subject: [PATCH 1/1] volume-api: Add libvolume-api.so This library implements the "core" of the new volume system, which will be used by several modules. Change-Id: Ib25ada1392e83237a3908e6064ee0ad6dff7afaf Signed-off-by: Jaska Uimonen --- Makefile.am | 3 + src/Makefile.am | 19 +- src/map-file | 15 + src/modules/volume-api/audio-group.c | 448 +++++++++++++ src/modules/volume-api/audio-group.h | 85 +++ src/modules/volume-api/binding.c | 386 +++++++++++ src/modules/volume-api/binding.h | 128 ++++ src/modules/volume-api/bvolume.h | 43 ++ src/modules/volume-api/device-creator.c | 1108 +++++++++++++++++++++++++++++++ src/modules/volume-api/device-creator.h | 32 + src/modules/volume-api/device.c | 293 ++++++++ src/modules/volume-api/device.h | 76 +++ src/modules/volume-api/mute-control.c | 306 +++++++++ src/modules/volume-api/mute-control.h | 102 +++ src/modules/volume-api/sstream.c | 366 ++++++++++ src/modules/volume-api/sstream.h | 108 +++ src/modules/volume-api/stream-creator.c | 691 +++++++++++++++++++ src/modules/volume-api/stream-creator.h | 32 + src/modules/volume-api/volume-api.c | 647 ++++++++++++++++++ src/modules/volume-api/volume-api.h | 163 +++++ src/modules/volume-api/volume-control.c | 363 ++++++++++ src/modules/volume-api/volume-control.h | 112 ++++ src/pulse/ext-volume-api.c | 275 ++++++++ src/pulse/ext-volume-api.h | 68 ++ 24 files changed, 5868 insertions(+), 1 deletion(-) create mode 100644 src/modules/volume-api/audio-group.c create mode 100644 src/modules/volume-api/audio-group.h create mode 100644 src/modules/volume-api/binding.c create mode 100644 src/modules/volume-api/binding.h create mode 100644 src/modules/volume-api/bvolume.h create mode 100644 src/modules/volume-api/device-creator.c create mode 100644 src/modules/volume-api/device-creator.h create mode 100644 src/modules/volume-api/device.c create mode 100644 src/modules/volume-api/device.h create mode 100644 src/modules/volume-api/mute-control.c create mode 100644 src/modules/volume-api/mute-control.h create mode 100644 src/modules/volume-api/sstream.c create mode 100644 src/modules/volume-api/sstream.h create mode 100644 src/modules/volume-api/stream-creator.c create mode 100644 src/modules/volume-api/stream-creator.h create mode 100644 src/modules/volume-api/volume-api.c create mode 100644 src/modules/volume-api/volume-api.h create mode 100644 src/modules/volume-api/volume-control.c create mode 100644 src/modules/volume-api/volume-control.h create mode 100644 src/pulse/ext-volume-api.c create mode 100644 src/pulse/ext-volume-api.h diff --git a/Makefile.am b/Makefile.am index 9431d4a..cf4a648 100644 --- a/Makefile.am +++ b/Makefile.am @@ -57,6 +57,9 @@ moduledevinternal_DATA = src/pulse/internal.h src/pulse/client-conf.h src/pulse/fork-detect.h moduledevinternaldir = $(includedir)/pulsemodule/pulse +moduledevvolumeapi_DATA = $(top_srcdir)/src/modules/volume-api/*.h +moduledevvolumeapidir = $(includedir)/pulsemodule/modules/volume-api + if HAVE_GLIB20 pkgconfig_DATA += \ libpulse-mainloop-glib.pc diff --git a/src/Makefile.am b/src/Makefile.am index 22b9b81..bc41dca 100644 --- a/src/Makefile.am 2016-04-13 15:51:34.439019531 +0200 +++ b/src/Makefile.am 2016-04-13 15:53:03.721019382 +0200 @@ -792,6 +792,7 @@ pulse/ext-device-manager.h \ pulse/ext-device-restore.h \ pulse/ext-stream-restore.h \ + pulse/ext-volume-api.h \ pulse/format.h \ pulse/gccmacro.h \ pulse/introspect.h \ @@ -838,6 +839,7 @@ pulse/ext-device-manager.c pulse/ext-device-manager.h \ pulse/ext-device-restore.c pulse/ext-device-restore.h \ pulse/ext-stream-restore.c pulse/ext-stream-restore.h \ + pulse/ext-volume-api.c pulse/ext-volume-api.h \ pulse/format.c pulse/format.h \ pulse/gccmacro.h \ pulse/internal.h \ @@ -1018,7 +1020,8 @@ modlibexec_LTLIBRARIES = \ libprotocol-cli.la \ libprotocol-simple.la \ libprotocol-http.la \ - libprotocol-native.la + libprotocol-native.la \ + libvolume-api.la if HAVE_WEBRTC modlibexec_LTLIBRARIES += libwebrtc-util.la @@ -1065,6 +1068,20 @@ libprotocol_native_la_CFLAGS += $(DBUS_CFLAGS) libprotocol_native_la_LIBADD += $(DBUS_LIBS) endif +libvolume_api_la_SOURCES = \ + modules/volume-api/audio-group.c modules/volume-api/audio-group.h \ + modules/volume-api/binding.c modules/volume-api/binding.h \ + modules/volume-api/bvolume.h \ + modules/volume-api/device.c modules/volume-api/device.h \ + modules/volume-api/device-creator.c modules/volume-api/device-creator.h \ + modules/volume-api/mute-control.c modules/volume-api/mute-control.h \ + modules/volume-api/sstream.c modules/volume-api/sstream.h \ + modules/volume-api/stream-creator.c modules/volume-api/stream-creator.h \ + modules/volume-api/volume-api.c modules/volume-api/volume-api.h \ + modules/volume-api/volume-control.c modules/volume-api/volume-control.h +libvolume_api_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version +libvolume_api_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la + if HAVE_ESOUND libprotocol_esound_la_SOURCES = pulsecore/protocol-esound.c pulsecore/protocol-esound.h pulsecore/esound.h libprotocol_esound_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version diff --git a/src/map-file b/src/map-file index fbf3f22..1f64a2f 100644 --- a/src/map-file +++ b/src/map-file @@ -172,6 +172,21 @@ pa_ext_stream_restore_subscribe; pa_ext_stream_restore_test; pa_ext_stream_restore_write; +pa_ext_volume_api_balance_valid; +pa_ext_volume_api_bvolume_copy_balance; +pa_ext_volume_api_bvolume_get_left_right_balance; +pa_ext_volume_api_bvolume_get_rear_front_balance; +pa_ext_volume_api_bvolume_equal; +pa_ext_volume_api_bvolume_from_cvolume; +pa_ext_volume_api_bvolume_init_invalid; +pa_ext_volume_api_bvolume_init_mono; +pa_ext_volume_api_bvolume_remap; +pa_ext_volume_api_bvolume_reset_balance; +pa_ext_volume_api_bvolume_set_left_right_balance; +pa_ext_volume_api_bvolume_set_rear_front_balance; +pa_ext_volume_api_bvolume_snprint_balance; +pa_ext_volume_api_bvolume_to_cvolume; +pa_ext_volume_api_bvolume_valid; pa_format_info_copy; pa_format_info_free; pa_format_info_from_string; diff --git a/src/modules/volume-api/audio-group.c b/src/modules/volume-api/audio-group.c new file mode 100644 index 0000000..76bfa69 --- /dev/null +++ b/src/modules/volume-api/audio-group.c @@ -0,0 +1,448 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "audio-group.h" + +#include + +#include + +int pa_audio_group_new(pa_volume_api *api, const char *name, const char *description, pa_audio_group **group) { + pa_audio_group *group_local; + int r; + + pa_assert(api); + pa_assert(name); + pa_assert(description); + pa_assert(group); + + group_local = pa_xnew0(pa_audio_group, 1); + group_local->volume_api = api; + group_local->index = pa_volume_api_allocate_audio_group_index(api); + + r = pa_volume_api_register_name(api, name, true, &group_local->name); + if (r < 0) + goto fail; + + group_local->description = pa_xstrdup(description); + group_local->proplist = pa_proplist_new(); + group_local->volume_streams = pa_hashmap_new(NULL, NULL); + group_local->mute_streams = pa_hashmap_new(NULL, NULL); + + *group = group_local; + + return 0; + +fail: + pa_audio_group_free(group_local); + + return r; +} + +void pa_audio_group_put(pa_audio_group *group) { + const char *prop_key; + void *state = NULL; + + pa_assert(group); + + pa_volume_api_add_audio_group(group->volume_api, group); + + group->linked = true; + + pa_log_debug("Created audio group #%u.", group->index); + pa_log_debug(" Name: %s", group->name); + pa_log_debug(" Description: %s", group->description); + pa_log_debug(" Volume control: %s", group->volume_control ? group->volume_control->name : "(unset)"); + pa_log_debug(" Mute control: %s", group->mute_control ? group->mute_control->name : "(unset)"); + pa_log_debug(" Properties:"); + + while ((prop_key = pa_proplist_iterate(group->proplist, &state))) + pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(group->proplist, prop_key))); + + pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_PUT], group); +} + +void pa_audio_group_unlink(pa_audio_group *group) { + pas_stream *stream; + + pa_assert(group); + + if (group->unlinked) { + pa_log_debug("Unlinking audio group %s (already unlinked, this is a no-op).", group->name); + return; + } + + group->unlinked = true; + + pa_log_debug("Unlinking audio group %s.", group->name); + + if (group->linked) + pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK], group); + + pa_volume_api_remove_audio_group(group->volume_api, group); + + while ((stream = pa_hashmap_first(group->mute_streams))) + pas_stream_set_audio_group_for_mute(stream, NULL); + + while ((stream = pa_hashmap_first(group->volume_streams))) + pas_stream_set_audio_group_for_volume(stream, NULL); + + if (group->mute_control_binding) { + pa_binding_free(group->mute_control_binding); + group->mute_control_binding = NULL; + } + + if (group->volume_control_binding) { + pa_binding_free(group->volume_control_binding); + group->volume_control_binding = NULL; + } + + pa_audio_group_set_have_own_mute_control(group, false); + pa_audio_group_set_have_own_volume_control(group, false); + + if (group->mute_control) { + pa_mute_control_remove_audio_group(group->mute_control, group); + group->mute_control = NULL; + } + + if (group->volume_control) { + pa_volume_control_remove_audio_group(group->volume_control, group); + group->volume_control = NULL; + } +} + +void pa_audio_group_free(pa_audio_group *group) { + pa_assert(group); + + if (!group->unlinked) + pa_audio_group_unlink(group); + + if (group->mute_streams) + pa_hashmap_free(group->mute_streams); + + if (group->volume_streams) + pa_hashmap_free(group->volume_streams); + + if (group->proplist) + pa_proplist_free(group->proplist); + + pa_xfree(group->description); + + if (group->name) + pa_volume_api_unregister_name(group->volume_api, group->name); + + pa_xfree(group); +} + +const char *pa_audio_group_get_name(pa_audio_group *group) { + pa_assert(group); + + return group->name; +} + +static int volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, + bool set_balance) { + pa_audio_group *group; + pas_stream *stream; + void *state; + + pa_assert(control); + pa_assert(volume); + + group = control->userdata; + + PA_HASHMAP_FOREACH(stream, group->volume_streams, state) { + if (stream->own_volume_control) + pa_volume_control_set_volume(stream->own_volume_control, volume, set_volume, set_balance); + } + + return 0; +} + +static void volume_control_set_initial_volume_cb(pa_volume_control *control) { + pa_audio_group *group; + pas_stream *stream; + void *state; + + pa_assert(control); + + group = control->userdata; + + PA_HASHMAP_FOREACH(stream, group->volume_streams, state) { + if (stream->own_volume_control) + pa_volume_control_set_volume(stream->own_volume_control, &control->volume, true, true); + } +} + +void pa_audio_group_set_have_own_volume_control(pa_audio_group *group, bool have) { + pa_assert(group); + + if (have == group->have_own_volume_control) + return; + + if (have) { + pa_bvolume initial_volume; + + if (group->volume_api->core->flat_volumes) + /* Usually the initial volume should get overridden by some module + * that manages audio group volume levels, but if there's no such + * module, let's try to avoid too high volume in flat volume + * mode. */ + pa_bvolume_init_mono(&initial_volume, 0.3 * PA_VOLUME_NORM); + else + pa_bvolume_init_mono(&initial_volume, PA_VOLUME_NORM); + + pa_assert(!group->own_volume_control); + group->own_volume_control = pa_volume_control_new(group->volume_api, "audio-group-volume-control", + group->description, false, false); + pa_volume_control_set_owner_audio_group(group->own_volume_control, group); + group->own_volume_control->set_volume = volume_control_set_volume_cb; + group->own_volume_control->userdata = group; + pa_volume_control_put(group->own_volume_control, &initial_volume, volume_control_set_initial_volume_cb); + } else { + pa_volume_control_free(group->own_volume_control); + group->own_volume_control = NULL; + } + + group->have_own_volume_control = have; +} + +static int mute_control_set_mute_cb(pa_mute_control *control, bool mute) { + pa_audio_group *group; + pas_stream *stream; + void *state; + + pa_assert(control); + + group = control->userdata; + + PA_HASHMAP_FOREACH(stream, group->mute_streams, state) { + if (stream->own_mute_control) + pa_mute_control_set_mute(stream->own_mute_control, mute); + } + + return 0; +} + +static void mute_control_set_initial_mute_cb(pa_mute_control *control) { + pa_audio_group *group; + pas_stream *stream; + void *state; + + pa_assert(control); + + group = control->userdata; + + PA_HASHMAP_FOREACH(stream, group->mute_streams, state) { + if (stream->own_mute_control) + pa_mute_control_set_mute(stream->own_mute_control, control->mute); + } +} + +void pa_audio_group_set_have_own_mute_control(pa_audio_group *group, bool have) { + pa_assert(group); + + if (have == group->have_own_mute_control) + return; + + group->have_own_mute_control = have; + + if (have) { + pa_assert(!group->own_mute_control); + group->own_mute_control = pa_mute_control_new(group->volume_api, "audio-group-mute-control", group->description); + pa_mute_control_set_owner_audio_group(group->own_mute_control, group); + group->own_mute_control->set_mute = mute_control_set_mute_cb; + group->own_mute_control->userdata = group; + pa_mute_control_put(group->own_mute_control, false, true, mute_control_set_initial_mute_cb); + } else { + pa_mute_control_free(group->own_mute_control); + group->own_mute_control = NULL; + } +} + +static void set_volume_control_internal(pa_audio_group *group, pa_volume_control *control) { + pa_volume_control *old_control; + + pa_assert(group); + + old_control = group->volume_control; + + if (control == old_control) + return; + + if (old_control) + pa_volume_control_remove_audio_group(old_control, group); + + group->volume_control = control; + + if (control) + pa_volume_control_add_audio_group(control, group); + + if (!group->linked || group->unlinked) + return; + + pa_log_debug("The volume control of audio group %s changed from %s to %s.", group->name, + old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); + + pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_VOLUME_CONTROL_CHANGED], group); +} + +void pa_audio_group_set_volume_control(pa_audio_group *group, pa_volume_control *control) { + pa_assert(group); + + if (group->volume_control_binding) { + pa_binding_free(group->volume_control_binding); + group->volume_control_binding = NULL; + } + + set_volume_control_internal(group, control); +} + +static void set_mute_control_internal(pa_audio_group *group, pa_mute_control *control) { + pa_mute_control *old_control; + + pa_assert(group); + + old_control = group->mute_control; + + if (control == old_control) + return; + + if (old_control) + pa_mute_control_remove_audio_group(old_control, group); + + group->mute_control = control; + + if (control) + pa_mute_control_add_audio_group(control, group); + + if (!group->linked || group->unlinked) + return; + + pa_log_debug("The mute control of audio group %s changed from %s to %s.", group->name, + old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); + + pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_MUTE_CONTROL_CHANGED], group); +} + +void pa_audio_group_set_mute_control(pa_audio_group *group, pa_mute_control *control) { + pa_assert(group); + + if (group->mute_control_binding) { + pa_binding_free(group->mute_control_binding); + group->mute_control_binding = NULL; + } + + set_mute_control_internal(group, control); +} + +void pa_audio_group_bind_volume_control(pa_audio_group *group, pa_binding_target_info *target_info) { + pa_binding_owner_info owner_info = { + .userdata = group, + .set_value = (pa_binding_set_value_cb_t) set_volume_control_internal, + }; + + pa_assert(group); + pa_assert(target_info); + + if (group->volume_control_binding) + pa_binding_free(group->volume_control_binding); + + group->volume_control_binding = pa_binding_new(group->volume_api, &owner_info, target_info); +} + +void pa_audio_group_bind_mute_control(pa_audio_group *group, pa_binding_target_info *target_info) { + pa_binding_owner_info owner_info = { + .userdata = group, + .set_value = (pa_binding_set_value_cb_t) set_mute_control_internal, + }; + + pa_assert(group); + pa_assert(target_info); + + if (group->mute_control_binding) + pa_binding_free(group->mute_control_binding); + + group->mute_control_binding = pa_binding_new(group->volume_api, &owner_info, target_info); +} + +void pa_audio_group_add_volume_stream(pa_audio_group *group, pas_stream *stream) { + pa_assert(group); + pa_assert(stream); + + pa_assert_se(pa_hashmap_put(group->volume_streams, stream, stream) >= 0); + + if (stream->own_volume_control && group->own_volume_control) + pa_volume_control_set_volume(stream->own_volume_control, &group->own_volume_control->volume, true, true); + + pa_log_debug("Stream %s added to audio group %s (volume).", stream->name, group->name); +} + +void pa_audio_group_remove_volume_stream(pa_audio_group *group, pas_stream *stream) { + pa_assert(group); + pa_assert(stream); + + pa_assert_se(pa_hashmap_remove(group->volume_streams, stream)); + + pa_log_debug("Stream %s removed from audio group %s (volume).", stream->name, group->name); +} + +void pa_audio_group_add_mute_stream(pa_audio_group *group, pas_stream *stream) { + pa_assert(group); + pa_assert(stream); + + pa_assert_se(pa_hashmap_put(group->mute_streams, stream, stream) >= 0); + + if (stream->own_mute_control && group->own_mute_control) + pa_mute_control_set_mute(stream->own_mute_control, group->own_mute_control->mute); + + pa_log_debug("Stream %s added to audio group %s (mute).", stream->name, group->name); +} + +void pa_audio_group_remove_mute_stream(pa_audio_group *group, pas_stream *stream) { + pa_assert(group); + pa_assert(stream); + + pa_assert_se(pa_hashmap_remove(group->mute_streams, stream)); + + pa_log_debug("Stream %s removed from audio group %s (mute).", stream->name, group->name); +} + +pa_binding_target_type *pa_audio_group_create_binding_target_type(pa_volume_api *api) { + pa_binding_target_type *type; + + pa_assert(api); + + type = pa_binding_target_type_new(PA_AUDIO_GROUP_BINDING_TARGET_TYPE, api->audio_groups, + &api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_PUT], + &api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK], + (pa_binding_target_type_get_name_cb_t) pa_audio_group_get_name); + pa_binding_target_type_add_field(type, PA_AUDIO_GROUP_BINDING_TARGET_FIELD_VOLUME_CONTROL, + PA_BINDING_CALCULATE_FIELD_OFFSET(pa_audio_group, volume_control)); + pa_binding_target_type_add_field(type, PA_AUDIO_GROUP_BINDING_TARGET_FIELD_MUTE_CONTROL, + PA_BINDING_CALCULATE_FIELD_OFFSET(pa_audio_group, mute_control)); + + return type; +} diff --git a/src/modules/volume-api/audio-group.h b/src/modules/volume-api/audio-group.h new file mode 100644 index 0000000..41591ba --- /dev/null +++ b/src/modules/volume-api/audio-group.h @@ -0,0 +1,85 @@ +#ifndef fooaudiogrouphfoo +#define fooaudiogrouphfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include +#include +#include + +#include + +#include + +typedef struct pa_audio_group pa_audio_group; + +#define PA_AUDIO_GROUP_BINDING_TARGET_TYPE "AudioGroup" +#define PA_AUDIO_GROUP_BINDING_TARGET_FIELD_VOLUME_CONTROL "volume_control" +#define PA_AUDIO_GROUP_BINDING_TARGET_FIELD_MUTE_CONTROL "mute_control" + +struct pa_audio_group { + pa_volume_api *volume_api; + uint32_t index; + const char *name; + char *description; + pa_proplist *proplist; + pa_volume_control *volume_control; + pa_mute_control *mute_control; + bool have_own_volume_control; + bool have_own_mute_control; + pa_volume_control *own_volume_control; + pa_mute_control *own_mute_control; + + pa_binding *volume_control_binding; + pa_binding *mute_control_binding; + pa_hashmap *volume_streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ + pa_hashmap *mute_streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ + + bool linked; + bool unlinked; +}; + +int pa_audio_group_new(pa_volume_api *api, const char *name, const char *description, pa_audio_group **group); +void pa_audio_group_put(pa_audio_group *group); +void pa_audio_group_unlink(pa_audio_group *group); +void pa_audio_group_free(pa_audio_group *group); + +const char *pa_audio_group_get_name(pa_audio_group *group); + +/* Called by policy modules. */ +void pa_audio_group_set_have_own_volume_control(pa_audio_group *group, bool have); +void pa_audio_group_set_have_own_mute_control(pa_audio_group *group, bool have); +void pa_audio_group_set_volume_control(pa_audio_group *group, pa_volume_control *control); +void pa_audio_group_set_mute_control(pa_audio_group *group, pa_mute_control *control); +void pa_audio_group_bind_volume_control(pa_audio_group *group, pa_binding_target_info *target_info); +void pa_audio_group_bind_mute_control(pa_audio_group *group, pa_binding_target_info *target_info); + +/* Called from sstream.c only. */ +void pa_audio_group_add_volume_stream(pa_audio_group *group, pas_stream *stream); +void pa_audio_group_remove_volume_stream(pa_audio_group *group, pas_stream *stream); +void pa_audio_group_add_mute_stream(pa_audio_group *group, pas_stream *stream); +void pa_audio_group_remove_mute_stream(pa_audio_group *group, pas_stream *stream); + +/* Called from volume-api.c only. */ +pa_binding_target_type *pa_audio_group_create_binding_target_type(pa_volume_api *api); + +#endif diff --git a/src/modules/volume-api/binding.c b/src/modules/volume-api/binding.c new file mode 100644 index 0000000..6e73119 --- /dev/null +++ b/src/modules/volume-api/binding.c @@ -0,0 +1,386 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "binding.h" + +#include +#include + +#include +#include + +struct field_entry { + char *name; + size_t offset; +}; + +static void set_target_type(pa_binding *binding, pa_binding_target_type *type); +static void set_target_object(pa_binding *binding, void *object); + +pa_binding_owner_info *pa_binding_owner_info_new(pa_binding_set_value_cb_t set_value, void *userdata) { + pa_binding_owner_info *info; + + pa_assert(set_value); + + info = pa_xnew0(pa_binding_owner_info, 1); + info->set_value = set_value; + info->userdata = userdata; + + return info; +} + +pa_binding_owner_info *pa_binding_owner_info_copy(const pa_binding_owner_info *info) { + pa_assert(info); + + return pa_binding_owner_info_new(info->set_value, info->userdata); +} + +void pa_binding_owner_info_free(pa_binding_owner_info *info) { + pa_assert(info); + + pa_xfree(info); +} + +pa_binding_target_info *pa_binding_target_info_new(const char *type, const char *name, const char *field) { + pa_binding_target_info *info; + + pa_assert(type); + pa_assert(name); + pa_assert(field); + + info = pa_xnew0(pa_binding_target_info, 1); + info->type = pa_xstrdup(type); + info->name = pa_xstrdup(name); + info->field = pa_xstrdup(field); + + return info; +} + +int pa_binding_target_info_new_from_string(const char *str, const char *field, pa_binding_target_info **info) { + const char *colon; + char *type = NULL; + char *name = NULL; + + pa_assert(str); + pa_assert(field); + pa_assert(info); + + if (!pa_startswith(str, "bind:")) + goto fail; + + colon = strchr(str + 5, ':'); + if (!colon) + goto fail; + + type = pa_xstrndup(str + 5, colon - (str + 5)); + + if (!*type) + goto fail; + + name = pa_xstrdup(colon + 1); + + if (!*name) + goto fail; + + *info = pa_binding_target_info_new(type, name, field); + pa_xfree(name); + pa_xfree(type); + + return 0; + +fail: + pa_log("Invalid binding target: %s", str); + pa_xfree(name); + pa_xfree(type); + + return -PA_ERR_INVALID; +} + +pa_binding_target_info *pa_binding_target_info_copy(const pa_binding_target_info *info) { + pa_assert(info); + + return pa_binding_target_info_new(info->type, info->name, info->field); +} + +void pa_binding_target_info_free(pa_binding_target_info *info) { + pa_assert(info); + + pa_xfree(info->field); + pa_xfree(info->name); + pa_xfree(info->type); + pa_xfree(info); +} + +static void field_entry_free(struct field_entry *entry) { + pa_assert(entry); + + pa_xfree(entry->name); + pa_xfree(entry); +} + +pa_binding_target_type *pa_binding_target_type_new(const char *name, pa_hashmap *objects, pa_hook *put_hook, + pa_hook *unlink_hook, pa_binding_target_type_get_name_cb_t get_name) { + pa_binding_target_type *type; + + pa_assert(name); + pa_assert(objects); + pa_assert(put_hook); + pa_assert(unlink_hook); + pa_assert(get_name); + + type = pa_xnew0(pa_binding_target_type, 1); + type->name = pa_xstrdup(name); + type->objects = objects; + type->put_hook = put_hook; + type->unlink_hook = unlink_hook; + type->get_name = get_name; + type->fields = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) field_entry_free); + + return type; +} + +void pa_binding_target_type_free(pa_binding_target_type *type) { + pa_assert(type); + + if (type->fields) + pa_hashmap_free(type->fields); + + pa_xfree(type->name); + pa_xfree(type); +} + +void pa_binding_target_type_add_field(pa_binding_target_type *type, const char *name, size_t offset) { + struct field_entry *entry; + + pa_assert(type); + pa_assert(name); + + entry = pa_xnew0(struct field_entry, 1); + entry->name = pa_xstrdup(name); + entry->offset = offset; + + pa_assert_se(pa_hashmap_put(type->fields, entry->name, entry) >= 0); +} + +int pa_binding_target_type_get_field_offset(pa_binding_target_type *type, const char *field, size_t *offset) { + struct field_entry *entry; + + pa_assert(type); + pa_assert(field); + pa_assert(offset); + + entry = pa_hashmap_get(type->fields, field); + if (!entry) + return -PA_ERR_NOENTITY; + + *offset = entry->offset; + + return 0; +} + +static pa_hook_result_t target_type_added_cb(void *hook_data, void *call_data, void *userdata) { + pa_binding_target_type *type = call_data; + pa_binding *binding = userdata; + + pa_assert(type); + pa_assert(binding); + + if (!pa_streq(type->name, binding->target_info->type)) + return PA_HOOK_OK; + + set_target_type(binding, type); + + return PA_HOOK_OK; +} + +static pa_hook_result_t target_type_removed_cb(void *hook_data, void *call_data, void *userdata) { + pa_binding_target_type *type = call_data; + pa_binding *binding = userdata; + + pa_assert(type); + pa_assert(binding); + + if (type != binding->target_type) + return PA_HOOK_OK; + + set_target_type(binding, NULL); + + return PA_HOOK_OK; +} + +static pa_hook_result_t target_put_cb(void *hook_data, void *call_data, void *userdata) { + pa_binding *binding = userdata; + + pa_assert(call_data); + pa_assert(binding); + + if (!pa_streq(binding->target_type->get_name(call_data), binding->target_info->name)) + return PA_HOOK_OK; + + set_target_object(binding, call_data); + + return PA_HOOK_OK; +} + +static pa_hook_result_t target_unlink_cb(void *hook_data, void *call_data, void *userdata) { + pa_binding *binding = userdata; + + pa_assert(call_data); + pa_assert(binding); + + if (call_data != binding->target_object) + return PA_HOOK_OK; + + set_target_object(binding, NULL); + + return PA_HOOK_OK; +} + +static void set_target_object(pa_binding *binding, void *object) { + pa_assert(binding); + + binding->target_object = object; + + if (object) { + if (binding->target_put_slot) { + pa_hook_slot_free(binding->target_put_slot); + binding->target_put_slot = NULL; + } + + if (!binding->target_unlink_slot) + binding->target_unlink_slot = pa_hook_connect(binding->target_type->unlink_hook, PA_HOOK_NORMAL, target_unlink_cb, + binding); + + if (binding->target_field_offset_valid) + binding->owner_info->set_value(binding->owner_info->userdata, + *((void **) (((uint8_t *) object) + binding->target_field_offset))); + else + binding->owner_info->set_value(binding->owner_info->userdata, NULL); + } else { + if (binding->target_unlink_slot) { + pa_hook_slot_free(binding->target_unlink_slot); + binding->target_unlink_slot = NULL; + } + + if (binding->target_type) { + if (!binding->target_put_slot) + binding->target_put_slot = pa_hook_connect(binding->target_type->put_hook, PA_HOOK_NORMAL, target_put_cb, binding); + } else { + if (binding->target_put_slot) { + pa_hook_slot_free(binding->target_put_slot); + binding->target_put_slot = NULL; + } + } + + binding->owner_info->set_value(binding->owner_info->userdata, NULL); + } +} + +static void set_target_type(pa_binding *binding, pa_binding_target_type *type) { + pa_assert(binding); + + binding->target_type = type; + + if (type) { + int r; + + if (binding->target_type_added_slot) { + pa_hook_slot_free(binding->target_type_added_slot); + binding->target_type_added_slot = NULL; + } + + if (!binding->target_type_removed_slot) + binding->target_type_removed_slot = + pa_hook_connect(&binding->volume_api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_REMOVED], + PA_HOOK_NORMAL, target_type_removed_cb, binding); + + r = pa_binding_target_type_get_field_offset(type, binding->target_info->field, &binding->target_field_offset); + if (r >= 0) + binding->target_field_offset_valid = true; + else { + pa_log_warn("Reference to non-existing field \"%s\" in binding target type \"%s\".", binding->target_info->field, + type->name); + binding->target_field_offset_valid = false; + } + + set_target_object(binding, pa_hashmap_get(type->objects, binding->target_info->name)); + } else { + if (binding->target_type_removed_slot) { + pa_hook_slot_free(binding->target_type_removed_slot); + binding->target_type_removed_slot = NULL; + } + + if (!binding->target_type_added_slot) + binding->target_type_added_slot = + pa_hook_connect(&binding->volume_api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_ADDED], + PA_HOOK_NORMAL, target_type_added_cb, binding); + + binding->target_field_offset_valid = false; + + set_target_object(binding, NULL); + } +} + +pa_binding *pa_binding_new(pa_volume_api *api, const pa_binding_owner_info *owner_info, + const pa_binding_target_info *target_info) { + pa_binding *binding; + + pa_assert(api); + pa_assert(owner_info); + pa_assert(target_info); + + binding = pa_xnew0(pa_binding, 1); + binding->volume_api = api; + binding->owner_info = pa_binding_owner_info_copy(owner_info); + binding->target_info = pa_binding_target_info_copy(target_info); + + set_target_type(binding, pa_hashmap_get(api->binding_target_types, target_info->type)); + + return binding; +} + +void pa_binding_free(pa_binding *binding) { + pa_assert(binding); + + if (binding->target_unlink_slot) + pa_hook_slot_free(binding->target_unlink_slot); + + if (binding->target_put_slot) + pa_hook_slot_free(binding->target_put_slot); + + if (binding->target_type_removed_slot) + pa_hook_slot_free(binding->target_type_removed_slot); + + if (binding->target_type_added_slot) + pa_hook_slot_free(binding->target_type_added_slot); + + if (binding->target_info) + pa_binding_target_info_free(binding->target_info); + + if (binding->owner_info) + pa_binding_owner_info_free(binding->owner_info); + + pa_xfree(binding); +} diff --git a/src/modules/volume-api/binding.h b/src/modules/volume-api/binding.h new file mode 100644 index 0000000..ba4dea8 --- /dev/null +++ b/src/modules/volume-api/binding.h @@ -0,0 +1,128 @@ +#ifndef foobindinghfoo +#define foobindinghfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +typedef struct pa_binding pa_binding; +typedef struct pa_binding_owner_info pa_binding_owner_info; +typedef struct pa_binding_target_info pa_binding_target_info; +typedef struct pa_binding_target_type pa_binding_target_type; + +typedef void (*pa_binding_set_value_cb_t)(void *userdata, void *value); + +struct pa_binding_owner_info { + /* This is the object that has the variable that the binding is created + * for. */ + void *userdata; + + /* Called when the owner object's value needs to be updated. The userdata + * parameter of the callback is the same as the userdata field in this + * struct, and the value parameter is the new value for whatever variable + * the binding was created for. */ + pa_binding_set_value_cb_t set_value; +}; + +pa_binding_owner_info *pa_binding_owner_info_new(pa_binding_set_value_cb_t set_value, void *userdata); +pa_binding_owner_info *pa_binding_owner_info_copy(const pa_binding_owner_info *info); +void pa_binding_owner_info_free(pa_binding_owner_info *info); + +struct pa_binding_target_info { + /* The target type name as registered with + * pa_binding_target_type_register(). */ + char *type; + + /* The target object name as returned by the get_name callback of + * pa_binding_target_type. */ + char *name; + + /* The target field of the target object. */ + char *field; +}; + +pa_binding_target_info *pa_binding_target_info_new(const char *type, const char *name, const char *field); + +/* The string format is "bind:TYPE:NAME". */ +int pa_binding_target_info_new_from_string(const char *str, const char *field, pa_binding_target_info **info); + +pa_binding_target_info *pa_binding_target_info_copy(const pa_binding_target_info *info); +void pa_binding_target_info_free(pa_binding_target_info *info); + +typedef const char *(*pa_binding_target_type_get_name_cb_t)(void *object); + +struct pa_binding_target_type { + /* Identifier for this target type. */ + char *name; + + /* name -> object. Points directly to some "master" object hashmap, so the + * hashmap is not owned by pa_binding_target_type. */ + pa_hashmap *objects; + + /* The hook that notifies of new objects if this target type. The call data + * of the hook must be a pointer to the new object (this should be true for + * all PUT hooks, so don't worry too much). */ + pa_hook *put_hook; + + /* The hook that notifies of unlinked objects of this target type. The call + * data of the hook must be a pointer to the removed object (this should be + * true for all UNLINK hooks, so don't worry too much). */ + pa_hook *unlink_hook; + + /* Function for getting the name of an object of this target type. */ + pa_binding_target_type_get_name_cb_t get_name; + + pa_hashmap *fields; +}; + +pa_binding_target_type *pa_binding_target_type_new(const char *name, pa_hashmap *objects, pa_hook *put_hook, + pa_hook *unlink_hook, pa_binding_target_type_get_name_cb_t get_name); +void pa_binding_target_type_free(pa_binding_target_type *type); + +/* Useful when calling pa_binding_target_type_add_field(). */ +#define PA_BINDING_CALCULATE_FIELD_OFFSET(type, field) ((size_t) &(((type *) 0)->field)) + +/* Called during the type initialization (right after + * pa_binding_target_type_new()). */ +void pa_binding_target_type_add_field(pa_binding_target_type *type, const char *name, size_t offset); + +int pa_binding_target_type_get_field_offset(pa_binding_target_type *type, const char *field, size_t *offset); + +struct pa_binding { + pa_volume_api *volume_api; + pa_binding_owner_info *owner_info; + pa_binding_target_info *target_info; + pa_binding_target_type *target_type; + void *target_object; + size_t target_field_offset; + bool target_field_offset_valid; + pa_hook_slot *target_type_added_slot; + pa_hook_slot *target_type_removed_slot; + pa_hook_slot *target_put_slot; + pa_hook_slot *target_unlink_slot; +}; + +pa_binding *pa_binding_new(pa_volume_api *api, const pa_binding_owner_info *owner_info, + const pa_binding_target_info *target_info); +void pa_binding_free(pa_binding *binding); + +#endif diff --git a/src/modules/volume-api/bvolume.h b/src/modules/volume-api/bvolume.h new file mode 100644 index 0000000..0317fb6 --- /dev/null +++ b/src/modules/volume-api/bvolume.h @@ -0,0 +1,43 @@ +#ifndef foobvolumehfoo +#define foobvolumehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +typedef pa_ext_volume_api_bvolume pa_bvolume; + +#define pa_balance_valid pa_ext_volume_api_balance_valid +#define pa_bvolume_valid pa_ext_volume_api_bvolume_valid +#define pa_bvolume_init_invalid pa_ext_volume_api_bvolume_init_invalid +#define pa_bvolume_init_mono pa_ext_volume_api_bvolume_init_mono +#define pa_bvolume_equal pa_ext_volume_api_bvolume_equal +#define pa_bvolume_from_cvolume pa_ext_volume_api_bvolume_from_cvolume +#define pa_bvolume_to_cvolume pa_ext_volume_api_bvolume_to_cvolume +#define pa_bvolume_copy_balance pa_ext_volume_api_bvolume_copy_balance +#define pa_bvolume_reset_balance pa_ext_volume_api_bvolume_reset_balance +#define pa_bvolume_remap pa_ext_volume_api_bvolume_remap + +#define PA_BVOLUME_SNPRINT_BALANCE_MAX PA_EXT_VOLUME_API_BVOLUME_SNPRINT_BALANCE_MAX +#define pa_bvolume_snprint_balance pa_ext_volume_api_bvolume_snprint_balance + +#endif diff --git a/src/modules/volume-api/device-creator.c b/src/modules/volume-api/device-creator.c new file mode 100644 index 0000000..1d912ba --- /dev/null +++ b/src/modules/volume-api/device-creator.c @@ -0,0 +1,1108 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "device-creator.h" + +#include +#include +#include + +#include +#include + +struct pa_device_creator { + pa_volume_api *volume_api; + pa_hashmap *devices; /* pa_device_port/pa_sink/pa_source -> struct device */ + pa_hook_slot *card_put_slot; + pa_hook_slot *card_unlink_slot; + pa_hook_slot *sink_put_slot; + pa_hook_slot *sink_unlink_slot; + pa_hook_slot *source_put_slot; + pa_hook_slot *source_unlink_slot; +}; + +enum device_type { + DEVICE_TYPE_PORT, + DEVICE_TYPE_PORT_MONITOR, + DEVICE_TYPE_SINK, + DEVICE_TYPE_SOURCE, +}; + +struct device_volume_control { + struct device *device; + pa_volume_control *volume_control; + + bool unlinked; + + pa_hook_slot *volume_changed_slot; +}; + +struct device_mute_control { + struct device *device; + pa_mute_control *mute_control; + + bool unlinked; + + pa_hook_slot *mute_changed_slot; +}; + +struct device { + pa_device_creator *creator; + enum device_type type; + pa_device_port *port; + pa_sink *sink; + pa_source *source; + pa_device *device; + struct device_volume_control *volume_control; + struct device_mute_control *mute_control; + + bool unlinked; + + pa_hook_slot *proplist_changed_slot; + pa_hook_slot *port_active_changed_slot; + struct device *monitor; +}; + +static const char *device_type_from_icon_name(const char *icon_name) { + if (!icon_name) + return NULL; + + if (pa_streq(icon_name, "audio-input-microphone")) + return "microphone"; + + if (pa_streq(icon_name, "audio-speakers")) + return "speakers"; + + if (pa_streq(icon_name, "audio-headphones")) + return "headphones"; + + return NULL; +} + +static const char *device_type_from_port_name(pa_device_port *port) { + pa_assert(port); + + if (strstr(port->name, "analog")) { + if (port->direction == PA_DIRECTION_INPUT) + return "analog-input"; + else + return "analog-output"; + } + + if (strstr(port->name, "hdmi")) { + if (port->direction == PA_DIRECTION_INPUT) + return "hdmi-input"; + else + return "hdmi-output"; + } + + if (strstr(port->name, "iec958")) { + if (port->direction == PA_DIRECTION_INPUT) + return "spdif-input"; + else + return "spdif-output"; + } + + return NULL; +} + +static const char *device_type_from_port(pa_device_port *port) { + const char *device_type; + + pa_assert(port); + + device_type = device_type_from_icon_name(pa_proplist_gets(port->proplist, PA_PROP_DEVICE_ICON_NAME)); + if (device_type) + return device_type; + + device_type = device_type_from_port_name(port); + if (device_type) + return device_type; + + return NULL; +} + +static const char *get_sink_description(pa_sink *sink) { + const char *description; + + pa_assert(sink); + + description = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION); + if (description) + return description; + + return sink->name; +} + +static const char *get_source_description(pa_source *source) { + const char *description; + + pa_assert(source); + + description = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION); + if (description) + return description; + + return source->name; +} + +static int volume_control_set_volume_cb(pa_volume_control *c, const pa_bvolume *volume, bool set_volume, bool set_balance) { + struct device_volume_control *control; + struct device *device; + pa_bvolume bvolume; + pa_cvolume cvolume; + + pa_assert(c); + pa_assert(volume); + + control = c->userdata; + device = control->device; + + switch (device->type) { + case DEVICE_TYPE_PORT: + if (device->port->direction == PA_DIRECTION_OUTPUT) + pa_bvolume_from_cvolume(&bvolume, &device->sink->reference_volume, &device->sink->channel_map); + else + pa_bvolume_from_cvolume(&bvolume, &device->source->reference_volume, &device->source->channel_map); + break; + + case DEVICE_TYPE_SINK: + pa_bvolume_from_cvolume(&bvolume, &device->sink->reference_volume, &device->sink->channel_map); + break; + + case DEVICE_TYPE_PORT_MONITOR: + case DEVICE_TYPE_SOURCE: + pa_bvolume_from_cvolume(&bvolume, &device->source->reference_volume, &device->source->channel_map); + break; + } + + if (set_volume) + bvolume.volume = volume->volume; + + if (set_balance) + pa_bvolume_copy_balance(&bvolume, volume); + + pa_bvolume_to_cvolume(&bvolume, &cvolume); + + switch (device->type) { + case DEVICE_TYPE_PORT: + if (device->port->direction == PA_DIRECTION_OUTPUT) + pa_sink_set_volume(device->sink, &cvolume, true, true); + else + pa_source_set_volume(device->source, &cvolume, true, true); + break; + + case DEVICE_TYPE_PORT_MONITOR: + case DEVICE_TYPE_SOURCE: + pa_source_set_volume(device->source, &cvolume, true, true); + break; + + case DEVICE_TYPE_SINK: + pa_sink_set_volume(device->sink, &cvolume, true, true); + break; + } + + return 0; +} + +static struct device_volume_control *device_volume_control_new(struct device *device) { + struct device_volume_control *control; + const char *name = NULL; + bool convertible_to_dB = false; + bool channel_map_is_writable; + + pa_assert(device); + + control = pa_xnew0(struct device_volume_control, 1); + control->device = device; + + switch (device->type) { + case DEVICE_TYPE_PORT: + name = "port-volume-control"; + + if (device->port->direction == PA_DIRECTION_OUTPUT) + convertible_to_dB = device->sink->flags & PA_SINK_DECIBEL_VOLUME; + else + convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME; + + break; + + case DEVICE_TYPE_PORT_MONITOR: + name = "port-monitor-volume-control"; + convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME; + break; + + case DEVICE_TYPE_SINK: + name = "sink-volume-control"; + convertible_to_dB = device->sink->flags & PA_SINK_DECIBEL_VOLUME; + break; + + case DEVICE_TYPE_SOURCE: + name = "source-volume-control"; + convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME; + break; + } + + channel_map_is_writable = false; + control->volume_control = pa_volume_control_new(device->creator->volume_api, name, device->device->description, + convertible_to_dB, channel_map_is_writable); + control->volume_control->set_volume = volume_control_set_volume_cb; + control->volume_control->userdata = control; + + return control; +} + +static pa_hook_result_t sink_or_source_volume_changed_cb(void *hook_data, void *call_data, void *userdata) { + struct device_volume_control *control = userdata; + struct device *device; + pa_sink *sink = NULL; + pa_source *source = NULL; + pa_bvolume bvolume; + + pa_assert(control); + pa_assert(call_data); + + device = control->device; + + switch (device->type) { + case DEVICE_TYPE_PORT: + if (device->port->direction == PA_DIRECTION_OUTPUT) + sink = call_data; + else + source = call_data; + break; + + case DEVICE_TYPE_PORT_MONITOR: + case DEVICE_TYPE_SOURCE: + source = call_data; + break; + + case DEVICE_TYPE_SINK: + sink = call_data; + break; + } + + if ((sink && sink != device->sink) || (source && source != device->source)) + return PA_HOOK_OK; + + if (sink) + pa_bvolume_from_cvolume(&bvolume, &sink->reference_volume, &sink->channel_map); + else + pa_bvolume_from_cvolume(&bvolume, &source->reference_volume, &source->channel_map); + + pa_volume_control_volume_changed(control->volume_control, &bvolume, true, true); + + return PA_HOOK_OK; +} + +static void volume_control_set_initial_volume_cb(pa_volume_control *c) { + struct device_volume_control *control; + struct device *device; + pa_cvolume cvolume; + + pa_assert(c); + + control = c->userdata; + device = control->device; + pa_bvolume_to_cvolume(&control->volume_control->volume, &cvolume); + + switch (device->type) { + case DEVICE_TYPE_PORT: + if (device->port->direction == PA_DIRECTION_OUTPUT) + pa_sink_set_volume(device->sink, &cvolume, true, true); + else + pa_source_set_volume(device->source, &cvolume, true, true); + break; + + case DEVICE_TYPE_PORT_MONITOR: + case DEVICE_TYPE_SOURCE: + pa_source_set_volume(device->source, &cvolume, true, true); + break; + + case DEVICE_TYPE_SINK: + pa_sink_set_volume(device->sink, &cvolume, true, true); + break; + } +} + +static void device_volume_control_put(struct device_volume_control *control) { + struct device *device; + pa_bvolume volume; + + pa_assert(control); + + device = control->device; + + switch (device->type) { + case DEVICE_TYPE_PORT: + if (device->port->direction == PA_DIRECTION_OUTPUT) { + control->volume_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], + PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); + pa_bvolume_from_cvolume(&volume, &device->sink->reference_volume, &device->sink->channel_map); + } else { + control->volume_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SOURCE_VOLUME_CHANGED], + PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); + pa_bvolume_from_cvolume(&volume, &device->source->reference_volume, &device->source->channel_map); + } + break; + + case DEVICE_TYPE_PORT_MONITOR: + case DEVICE_TYPE_SOURCE: + control->volume_changed_slot = pa_hook_connect(&device->source->core->hooks[PA_CORE_HOOK_SOURCE_VOLUME_CHANGED], + PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); + pa_bvolume_from_cvolume(&volume, &device->source->reference_volume, &device->source->channel_map); + break; + + case DEVICE_TYPE_SINK: + control->volume_changed_slot = pa_hook_connect(&device->sink->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], + PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); + pa_bvolume_from_cvolume(&volume, &device->sink->reference_volume, &device->sink->channel_map); + break; + } + + pa_volume_control_put(control->volume_control, &volume, volume_control_set_initial_volume_cb); +} + +static void device_volume_control_unlink(struct device_volume_control *control) { + pa_assert(control); + + if (control->unlinked) + return; + + control->unlinked = true; + + if (control->volume_control) + pa_volume_control_unlink(control->volume_control); + + if (control->volume_changed_slot) { + pa_hook_slot_free(control->volume_changed_slot); + control->volume_changed_slot = NULL; + } +} + +static void device_volume_control_free(struct device_volume_control *control) { + pa_assert(control); + + if (!control->unlinked) + device_volume_control_unlink(control); + + if (control->volume_control) + pa_volume_control_free(control->volume_control); + + pa_xfree(control); +} + +static int mute_control_set_mute_cb(pa_mute_control *c, bool mute) { + struct device_mute_control *control; + struct device *device; + + pa_assert(c); + + control = c->userdata; + device = control->device; + + switch (device->type) { + case DEVICE_TYPE_PORT: + if (device->port->direction == PA_DIRECTION_OUTPUT) + pa_sink_set_mute(device->sink, mute, true); + else + pa_source_set_mute(device->source, mute, true); + break; + + case DEVICE_TYPE_PORT_MONITOR: + case DEVICE_TYPE_SOURCE: + pa_source_set_mute(device->source, mute, true); + break; + + case DEVICE_TYPE_SINK: + pa_sink_set_mute(device->sink, mute, true); + break; + } + + return 0; +} + +static struct device_mute_control *device_mute_control_new(struct device *device) { + struct device_mute_control *control; + const char *name = NULL; + + pa_assert(device); + + control = pa_xnew0(struct device_mute_control, 1); + control->device = device; + + switch (device->type) { + case DEVICE_TYPE_PORT: + name = "port-mute-control"; + break; + + case DEVICE_TYPE_PORT_MONITOR: + name = "port-monitor-mute-control"; + break; + + case DEVICE_TYPE_SINK: + name = "sink-mute-control"; + break; + + case DEVICE_TYPE_SOURCE: + name = "source-mute-control"; + break; + } + + control->mute_control = pa_mute_control_new(device->creator->volume_api, name, device->device->description); + control->mute_control->set_mute = mute_control_set_mute_cb; + control->mute_control->userdata = control; + + return control; +} + +static pa_hook_result_t sink_or_source_mute_changed_cb(void *hook_data, void *call_data, void *userdata) { + struct device_mute_control *control = userdata; + struct device *device; + pa_sink *sink = NULL; + pa_source *source = NULL; + bool mute; + + pa_assert(control); + pa_assert(call_data); + + device = control->device; + + switch (device->type) { + case DEVICE_TYPE_PORT: + if (device->port->direction == PA_DIRECTION_OUTPUT) + sink = call_data; + else + source = call_data; + break; + + case DEVICE_TYPE_PORT_MONITOR: + case DEVICE_TYPE_SOURCE: + source = call_data; + break; + + case DEVICE_TYPE_SINK: + sink = call_data; + break; + } + + if ((sink && sink != device->sink) || (source && source != device->source)) + return PA_HOOK_OK; + + if (sink) + mute = sink->muted; + else + mute = source->muted; + + pa_mute_control_mute_changed(control->mute_control, mute); + + return PA_HOOK_OK; +} + +static void mute_control_set_initial_mute_cb(pa_mute_control *c) { + struct device_volume_control *control; + struct device *device; + + pa_assert(c); + + control = c->userdata; + device = control->device; + + switch (device->type) { + case DEVICE_TYPE_PORT: + if (device->port->direction == PA_DIRECTION_OUTPUT) + pa_sink_set_mute(device->sink, c->mute, true); + else + pa_source_set_mute(device->source, c->mute, true); + break; + + case DEVICE_TYPE_PORT_MONITOR: + case DEVICE_TYPE_SOURCE: + pa_source_set_mute(device->source, c->mute, true); + break; + + case DEVICE_TYPE_SINK: + pa_sink_set_mute(device->sink, c->mute, true); + break; + } +} + +static void device_mute_control_put(struct device_mute_control *control) { + struct device *device; + bool mute = false; + + pa_assert(control); + + device = control->device; + + switch (device->type) { + case DEVICE_TYPE_PORT: + if (device->port->direction == PA_DIRECTION_OUTPUT) { + control->mute_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SINK_MUTE_CHANGED], + PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); + mute = device->sink->muted; + } else { + control->mute_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SOURCE_MUTE_CHANGED], + PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); + mute = device->source->muted; + } + break; + + case DEVICE_TYPE_PORT_MONITOR: + case DEVICE_TYPE_SOURCE: + control->mute_changed_slot = pa_hook_connect(&device->source->core->hooks[PA_CORE_HOOK_SOURCE_MUTE_CHANGED], + PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); + mute = device->source->muted; + break; + + case DEVICE_TYPE_SINK: + control->mute_changed_slot = pa_hook_connect(&device->sink->core->hooks[PA_CORE_HOOK_SINK_MUTE_CHANGED], + PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); + mute = device->sink->muted; + break; + } + + pa_mute_control_put(control->mute_control, mute, true, mute_control_set_initial_mute_cb); +} + +static void device_mute_control_unlink(struct device_mute_control *control) { + pa_assert(control); + + if (control->unlinked) + return; + + control->unlinked = true; + + if (control->mute_control) + pa_mute_control_unlink(control->mute_control); + + if (control->mute_changed_slot) { + pa_hook_slot_free(control->mute_changed_slot); + control->mute_changed_slot = NULL; + } +} + +static void device_mute_control_free(struct device_mute_control *control) { + pa_assert(control); + + if (!control->unlinked) + device_mute_control_unlink(control); + + if (control->mute_control) + pa_mute_control_free(control->mute_control); + + pa_xfree(control); +} + +static void device_set_sink_and_source_from_port(struct device *device) { + pa_sink *sink; + pa_source *source; + uint32_t idx; + + pa_assert(device); + + device->sink = NULL; + device->source = NULL; + + if (!device->port->active) + return; + + switch (device->type) { + case DEVICE_TYPE_PORT: + if (device->port->direction == PA_DIRECTION_OUTPUT) { + PA_IDXSET_FOREACH(sink, device->port->card->sinks, idx) { + if (sink->active_port == device->port) { + device->sink = sink; + break; + } + } + + pa_assert(device->sink); + } else { + PA_IDXSET_FOREACH(source, device->port->card->sources, idx) { + if (source->active_port == device->port) { + device->source = source; + break; + } + } + + pa_assert(device->source); + } + break; + + case DEVICE_TYPE_PORT_MONITOR: { + PA_IDXSET_FOREACH(sink, device->port->card->sinks, idx) { + if (sink->active_port == device->port) { + device->sink = sink; + device->source = sink->monitor_source; + break; + } + } + + pa_assert(device->sink); + break; + } + + case DEVICE_TYPE_SINK: + case DEVICE_TYPE_SOURCE: + pa_assert_not_reached(); + } +} + +static pa_hook_result_t sink_or_source_proplist_changed_cb(void *hook_data, void *call_data, void *userdata) { + struct device *device = userdata; + pa_sink *sink = NULL; + pa_source *source = NULL; + const char *description = NULL; + + pa_assert(device); + pa_assert(call_data); + + switch (device->type) { + case DEVICE_TYPE_PORT: + case DEVICE_TYPE_PORT_MONITOR: + pa_assert_not_reached(); + + case DEVICE_TYPE_SINK: + sink = call_data; + + if (sink != device->sink) + return PA_HOOK_OK; + + description = get_sink_description(sink); + break; + + case DEVICE_TYPE_SOURCE: + source = call_data; + + if (source != device->source) + return PA_HOOK_OK; + + description = get_source_description(source); + break; + } + + pa_device_description_changed(device->device, description); + pa_volume_control_description_changed(device->volume_control->volume_control, description); + pa_mute_control_description_changed(device->mute_control->mute_control, description); + + return PA_HOOK_OK; +} + +static struct device *device_new(pa_device_creator *creator, enum device_type type, void *core_device) { + struct device *device = NULL; + const char *name = NULL; + char *description = NULL; + pa_direction_t direction = PA_DIRECTION_OUTPUT; + const char *device_type = NULL; + bool create_volume_and_mute_controls = true; + + pa_assert(creator); + pa_assert(core_device); + + device = pa_xnew0(struct device, 1); + device->creator = creator; + device->type = type; + + switch (type) { + case DEVICE_TYPE_PORT: + device->port = core_device; + device_set_sink_and_source_from_port(device); + name = "port-device"; + description = pa_xstrdup(device->port->description); + direction = device->port->direction; + device_type = device_type_from_port(device->port); + + if (!device->sink && !device->source) + create_volume_and_mute_controls = false; + break; + + case DEVICE_TYPE_PORT_MONITOR: + device->port = core_device; + device_set_sink_and_source_from_port(device); + name = "port-monitor-device"; + description = pa_sprintf_malloc(_("Monitor of %s"), device->port->description); + direction = PA_DIRECTION_INPUT; + + if (!device->source) + create_volume_and_mute_controls = false; + break; + + case DEVICE_TYPE_SINK: + device->sink = core_device; + name = "sink-device"; + description = pa_xstrdup(get_sink_description(device->sink)); + direction = PA_DIRECTION_OUTPUT; + break; + + case DEVICE_TYPE_SOURCE: + device->source = core_device; + name = "source-device"; + description = pa_xstrdup(get_source_description(device->source)); + direction = PA_DIRECTION_INPUT; + break; + } + + device->device = pa_device_new(creator->volume_api, name, description, direction, &device_type, device_type ? 1 : 0); + pa_xfree(description); + + if (create_volume_and_mute_controls) { + device->volume_control = device_volume_control_new(device); + device->mute_control = device_mute_control_new(device); + } + + switch (type) { + case DEVICE_TYPE_PORT: + if (device->port->direction == PA_DIRECTION_OUTPUT) + device->monitor = device_new(creator, DEVICE_TYPE_PORT_MONITOR, device->port); + break; + + case DEVICE_TYPE_PORT_MONITOR: + break; + + case DEVICE_TYPE_SINK: + device->proplist_changed_slot = pa_hook_connect(&device->sink->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], + PA_HOOK_NORMAL, sink_or_source_proplist_changed_cb, device); + break; + + case DEVICE_TYPE_SOURCE: + device->proplist_changed_slot = pa_hook_connect(&device->source->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], + PA_HOOK_NORMAL, sink_or_source_proplist_changed_cb, device); + break; + } + + return device; +} + +static pa_hook_result_t port_active_changed_cb(void *hook_data, void *call_data, void *userdata) { + struct device *device = userdata; + pa_device_port *port = call_data; + bool should_have_volume_and_mute_controls = false; + + pa_assert(device); + pa_assert(port); + + if (port != device->port) + return PA_HOOK_OK; + + device_set_sink_and_source_from_port(device); + + switch (device->type) { + case DEVICE_TYPE_PORT: + should_have_volume_and_mute_controls = device->sink || device->source; + break; + + case DEVICE_TYPE_PORT_MONITOR: + should_have_volume_and_mute_controls = !!device->source; + break; + + case DEVICE_TYPE_SINK: + case DEVICE_TYPE_SOURCE: + pa_assert_not_reached(); + } + + if (should_have_volume_and_mute_controls && !device->volume_control) { + pa_assert(!device->mute_control); + + device->volume_control = device_volume_control_new(device); + device_volume_control_put(device->volume_control); + pa_device_set_default_volume_control(device->device, device->volume_control->volume_control); + + device->mute_control = device_mute_control_new(device); + device_mute_control_put(device->mute_control); + pa_device_set_default_mute_control(device->device, device->mute_control->mute_control); + } + + if (!should_have_volume_and_mute_controls && device->volume_control) { + pa_assert(device->mute_control); + + device_mute_control_free(device->mute_control); + device->mute_control = NULL; + device_volume_control_free(device->volume_control); + device->volume_control = NULL; + } + + return PA_HOOK_OK; +} + +static void device_put(struct device *device) { + pa_assert(device); + + switch (device->type) { + case DEVICE_TYPE_PORT: + case DEVICE_TYPE_PORT_MONITOR: + device->port_active_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_PORT_ACTIVE_CHANGED], + PA_HOOK_NORMAL, port_active_changed_cb, device); + + case DEVICE_TYPE_SINK: + case DEVICE_TYPE_SOURCE: + break; + } + + if (device->volume_control) + device_volume_control_put(device->volume_control); + + if (device->mute_control) + device_mute_control_put(device->mute_control); + + pa_device_put(device->device, device->volume_control ? device->volume_control->volume_control : NULL, + device->mute_control ? device->mute_control->mute_control : NULL); + + if (device->monitor) + device_put(device->monitor); +} + +static void device_unlink(struct device *device) { + pa_assert(device); + + if (device->unlinked) + return; + + device->unlinked = true; + + if (device->monitor) + device_unlink(device->monitor); + + if (device->device) + pa_device_unlink(device->device); + + if (device->mute_control) + device_mute_control_unlink(device->mute_control); + + if (device->volume_control) + device_volume_control_unlink(device->volume_control); + + if (device->port_active_changed_slot) { + pa_hook_slot_free(device->port_active_changed_slot); + device->port_active_changed_slot = NULL; + } +} + +static void device_free(struct device *device) { + pa_assert(device); + + if (!device->unlinked) + device_unlink(device); + + if (device->monitor) + device_free(device->monitor); + + if (device->proplist_changed_slot) + pa_hook_slot_free(device->proplist_changed_slot); + + if (device->mute_control) + device_mute_control_free(device->mute_control); + + if (device->volume_control) + device_volume_control_free(device->volume_control); + + if (device->device) + pa_device_free(device->device); + + pa_xfree(device); +} + +static void create_device(pa_device_creator *creator, enum device_type type, void *core_device) { + struct device *device; + + pa_assert(creator); + pa_assert(core_device); + + switch (type) { + case DEVICE_TYPE_PORT: + break; + + case DEVICE_TYPE_PORT_MONITOR: + pa_assert_not_reached(); + + case DEVICE_TYPE_SINK: + if (!pa_hashmap_isempty(((pa_sink *) core_device)->ports)) + return; + break; + + case DEVICE_TYPE_SOURCE: { + pa_source *source = core_device; + + if (source->monitor_of && !pa_hashmap_isempty(source->monitor_of->ports)) + return; + + if (!pa_hashmap_isempty(((pa_source *) core_device)->ports)) + return; + break; + } + } + + device = device_new(creator, type, core_device); + pa_hashmap_put(creator->devices, core_device, device); + device_put(device); +} + +static pa_hook_result_t card_put_cb(void *hook_data, void *call_data, void *userdata) { + pa_device_creator *creator = userdata; + pa_card *card = call_data; + pa_device_port *port; + void *state; + + pa_assert(creator); + pa_assert(card); + + PA_HASHMAP_FOREACH(port, card->ports, state) + create_device(creator, DEVICE_TYPE_PORT, port); + + return PA_HOOK_OK; +} + +static pa_hook_result_t card_unlink_cb(void *hook_data, void *call_data, void *userdata) { + pa_device_creator *creator = userdata; + pa_card *card = call_data; + pa_device_port *port; + void *state; + + pa_assert(creator); + pa_assert(card); + + PA_HASHMAP_FOREACH(port, card->ports, state) + pa_hashmap_remove_and_free(creator->devices, port); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_put_cb(void *hook_data, void *call_data, void *userdata) { + pa_device_creator *creator = userdata; + pa_sink *sink = call_data; + + pa_assert(creator); + pa_assert(sink); + + create_device(creator, DEVICE_TYPE_SINK, sink); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_unlink_cb(void *hook_data, void *call_data, void *userdata) { + pa_device_creator *creator = userdata; + pa_sink *sink = call_data; + + pa_assert(creator); + pa_assert(sink); + + pa_hashmap_remove_and_free(creator->devices, sink); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_put_cb(void *hook_data, void *call_data, void *userdata) { + pa_device_creator *creator = userdata; + pa_source *source = call_data; + + pa_assert(creator); + pa_assert(source); + + create_device(creator, DEVICE_TYPE_SOURCE, source); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_unlink_cb(void *hook_data, void *call_data, void *userdata) { + pa_device_creator *creator = userdata; + pa_source *source = call_data; + + pa_assert(creator); + pa_assert(source); + + pa_hashmap_remove_and_free(creator->devices, source); + + return PA_HOOK_OK; +} + +pa_device_creator *pa_device_creator_new(pa_volume_api *api) { + pa_device_creator *creator; + pa_card *card; + uint32_t idx; + pa_sink *sink; + pa_source *source; + + pa_assert(api); + + creator = pa_xnew0(pa_device_creator, 1); + creator->volume_api = api; + creator->devices = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) device_free); + creator->card_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_CARD_PUT], PA_HOOK_NORMAL, card_put_cb, creator); + creator->card_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_CARD_UNLINK], PA_HOOK_NORMAL, card_unlink_cb, + creator); + creator->sink_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_NORMAL, sink_put_cb, creator); + creator->sink_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_NORMAL, sink_unlink_cb, + creator); + creator->source_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL, source_put_cb, + creator); + creator->source_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_NORMAL, + source_unlink_cb, creator); + + PA_IDXSET_FOREACH(card, api->core->cards, idx) { + pa_device_port *port; + void *state; + + PA_HASHMAP_FOREACH(port, card->ports, state) + create_device(creator, DEVICE_TYPE_PORT, port); + } + + PA_IDXSET_FOREACH(sink, api->core->sinks, idx) + create_device(creator, DEVICE_TYPE_SINK, sink); + + PA_IDXSET_FOREACH(source, api->core->sources, idx) + create_device(creator, DEVICE_TYPE_SOURCE, source); + + return creator; +} + +void pa_device_creator_free(pa_device_creator *creator) { + pa_assert(creator); + + if (creator->devices) + pa_hashmap_remove_all(creator->devices); + + if (creator->source_unlink_slot) + pa_hook_slot_free(creator->source_unlink_slot); + + if (creator->source_put_slot) + pa_hook_slot_free(creator->source_put_slot); + + if (creator->sink_unlink_slot) + pa_hook_slot_free(creator->sink_unlink_slot); + + if (creator->sink_put_slot) + pa_hook_slot_free(creator->sink_put_slot); + + if (creator->card_unlink_slot) + pa_hook_slot_free(creator->card_unlink_slot); + + if (creator->card_put_slot) + pa_hook_slot_free(creator->card_put_slot); + + if (creator->devices) + pa_hashmap_free(creator->devices); + + pa_xfree(creator); +} diff --git a/src/modules/volume-api/device-creator.h b/src/modules/volume-api/device-creator.h new file mode 100644 index 0000000..bcec8d9 --- /dev/null +++ b/src/modules/volume-api/device-creator.h @@ -0,0 +1,32 @@ +#ifndef foodevicecreatorhfoo +#define foodevicecreatorhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +typedef struct pa_device_creator pa_device_creator; + +pa_device_creator *pa_device_creator_new(pa_volume_api *api); +void pa_device_creator_free(pa_device_creator *creator); + +#endif diff --git a/src/modules/volume-api/device.c b/src/modules/volume-api/device.c new file mode 100644 index 0000000..ea496ba --- /dev/null +++ b/src/modules/volume-api/device.c @@ -0,0 +1,293 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "device.h" + +#include +#include + +#include + +#include + +pa_device *pa_device_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction, + const char * const *device_types, unsigned n_device_types) { + pa_device *device; + unsigned i; + + pa_assert(api); + pa_assert(name); + pa_assert(description); + pa_assert(device_types || n_device_types == 0); + + device = pa_xnew0(pa_device, 1); + device->volume_api = api; + device->index = pa_volume_api_allocate_device_index(api); + pa_assert_se(pa_volume_api_register_name(api, name, false, &device->name) >= 0); + device->description = pa_xstrdup(description); + device->direction = direction; + device->device_types = pa_dynarray_new(pa_xfree); + + for (i = 0; i < n_device_types; i++) + pa_dynarray_append(device->device_types, pa_xstrdup(device_types[i])); + + device->proplist = pa_proplist_new(); + device->use_default_volume_control = true; + device->use_default_mute_control = true; + + return device; +} + +void pa_device_put(pa_device *device, pa_volume_control *default_volume_control, pa_mute_control *default_mute_control) { + char *device_types_str; + const char *prop_key; + void *state = NULL; + + pa_assert(device); + + if (default_volume_control) { + device->default_volume_control = default_volume_control; + pa_volume_control_add_default_for_device(default_volume_control, device); + + device->volume_control = default_volume_control; + pa_volume_control_add_device(default_volume_control, device); + } + + if (default_mute_control) { + device->default_mute_control = default_mute_control; + pa_mute_control_add_default_for_device(default_mute_control, device); + + device->mute_control = default_mute_control; + pa_mute_control_add_device(default_mute_control, device); + } + + pa_volume_api_add_device(device->volume_api, device); + + device->linked = true; + + device_types_str = pa_join((const char * const *) pa_dynarray_get_raw_array(device->device_types), + pa_dynarray_size(device->device_types), ", "); + + pa_log_debug("Created device #%u.", device->index); + pa_log_debug(" Name: %s", device->name); + pa_log_debug(" Description: %s", device->description); + pa_log_debug(" Direction: %s", pa_direction_to_string(device->direction)); + pa_log_debug(" Device Types: %s", *device_types_str ? device_types_str : "(none)"); + pa_log_debug(" Volume control: %s", device->volume_control ? device->volume_control->name : "(unset)"); + pa_log_debug(" Mute control: %s", device->mute_control ? device->mute_control->name : "(unset)"); + pa_log_debug(" Properties:"); + + while ((prop_key = pa_proplist_iterate(device->proplist, &state))) + pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(device->proplist, prop_key))); + + pa_xfree(device_types_str); + + pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_PUT], device); +} + +void pa_device_unlink(pa_device *device) { + pa_assert(device); + + if (device->unlinked) { + pa_log_debug("Unlinking device %s (already unlinked, this is a no-op).", device->name); + return; + } + + device->unlinked = true; + + pa_log_debug("Unlinking device %s.", device->name); + + if (device->linked) + pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_UNLINK], device); + + pa_volume_api_remove_device(device->volume_api, device); + + if (device->mute_control) { + pa_mute_control_remove_device(device->mute_control, device); + device->mute_control = NULL; + } + + if (device->default_mute_control) { + pa_mute_control_remove_default_for_device(device->default_mute_control, device); + device->default_mute_control = NULL; + } + + if (device->volume_control) { + pa_volume_control_remove_device(device->volume_control, device); + device->volume_control = NULL; + } + + if (device->default_volume_control) { + pa_volume_control_remove_default_for_device(device->default_volume_control, device); + device->default_volume_control = NULL; + } +} + +void pa_device_free(pa_device *device) { + pa_assert(device); + + if (!device->unlinked) + pa_device_unlink(device); + + if (device->proplist) + pa_proplist_free(device->proplist); + + if (device->device_types) + pa_dynarray_free(device->device_types); + + pa_xfree(device->description); + + if (device->name) + pa_volume_api_unregister_name(device->volume_api, device->name); + + pa_xfree(device); +} + +static void set_volume_control_internal(pa_device *device, pa_volume_control *control) { + pa_volume_control *old_control; + + pa_assert(device); + + old_control = device->volume_control; + + if (control == old_control) + return; + + if (old_control) + pa_volume_control_remove_device(old_control, device); + + device->volume_control = control; + + if (control) + pa_volume_control_add_device(control, device); + + if (!device->linked || device->unlinked) + return; + + pa_log_debug("The volume control of device %s changed from %s to %s.", device->name, + old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); + + pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_VOLUME_CONTROL_CHANGED], device); +} + +void pa_device_set_volume_control(pa_device *device, pa_volume_control *control) { + pa_assert(device); + + device->use_default_volume_control = false; + set_volume_control_internal(device, control); +} + +static void set_mute_control_internal(pa_device *device, pa_mute_control *control) { + pa_mute_control *old_control; + + pa_assert(device); + + old_control = device->mute_control; + + if (control == old_control) + return; + + if (old_control) + pa_mute_control_remove_device(old_control, device); + + device->mute_control = control; + + if (control) + pa_mute_control_add_device(control, device); + + pa_log_debug("The mute control of device %s changed from %s to %s.", device->name, + old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); + + pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_MUTE_CONTROL_CHANGED], device); +} + +void pa_device_set_mute_control(pa_device *device, pa_mute_control *control) { + pa_assert(device); + + device->use_default_mute_control = false; + set_mute_control_internal(device, control); +} + +void pa_device_description_changed(pa_device *device, const char *new_description) { + char *old_description; + + pa_assert(device); + pa_assert(new_description); + + old_description = device->description; + + if (pa_streq(new_description, old_description)) + return; + + device->description = pa_xstrdup(new_description); + pa_log_debug("The description of device %s changed from \"%s\" to \"%s\".", device->name, old_description, + new_description); + pa_xfree(old_description); + pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_DESCRIPTION_CHANGED], device); +} + +void pa_device_set_default_volume_control(pa_device *device, pa_volume_control *control) { + pa_volume_control *old_control; + + pa_assert(device); + + old_control = device->default_volume_control; + + if (control == old_control) + return; + + if (old_control) + pa_volume_control_remove_default_for_device(old_control, device); + + device->default_volume_control = control; + + if (control) + pa_volume_control_add_default_for_device(control, device); + + if (device->use_default_volume_control) + set_volume_control_internal(device, control); +} + +void pa_device_set_default_mute_control(pa_device *device, pa_mute_control *control) { + pa_mute_control *old_control; + + pa_assert(device); + + old_control = device->default_mute_control; + + if (control == old_control) + return; + + if (old_control) + pa_mute_control_remove_default_for_device(old_control, device); + + device->default_mute_control = control; + + if (control) + pa_mute_control_add_default_for_device(control, device); + + if (device->use_default_mute_control) + set_mute_control_internal(device, control); +} diff --git a/src/modules/volume-api/device.h b/src/modules/volume-api/device.h new file mode 100644 index 0000000..9eac7e9 --- /dev/null +++ b/src/modules/volume-api/device.h @@ -0,0 +1,76 @@ +#ifndef foodevicehfoo +#define foodevicehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +#include + +typedef struct pa_device pa_device; + +struct pa_device { + pa_volume_api *volume_api; + uint32_t index; + const char *name; + char *description; + pa_direction_t direction; + pa_dynarray *device_types; + pa_proplist *proplist; + pa_volume_control *volume_control; + pa_mute_control *mute_control; + + /* The device implementation can provide default volume and mute controls, + * which are used in case there's no policy module that wants to override + * the defaults. */ + pa_volume_control *default_volume_control; + bool use_default_volume_control; + pa_mute_control *default_mute_control; + bool use_default_mute_control; + + bool linked; + bool unlinked; +}; + +pa_device *pa_device_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction, + const char * const *device_types, unsigned n_device_types); +void pa_device_put(pa_device *device, pa_volume_control *default_volume_control, pa_mute_control *default_mute_control); +void pa_device_unlink(pa_device *device); +void pa_device_free(pa_device *device); + +/* Called by policy modules. */ +void pa_device_set_volume_control(pa_device *device, pa_volume_control *control); +void pa_device_set_mute_control(pa_device *device, pa_mute_control *control); + +/* Called by policy modules. Note that pa_device_set_volume_control() and + * pa_device_set_mute_control() automatically disable the corresponding + * use_default flags, so these functions are mainly useful for re-enabling the + * flags. */ +void pa_device_set_use_default_volume_control(pa_device *device, bool use); +void pa_device_set_use_default_mute_control(pa_device *device, bool use); + +/* Called by the device implementation. */ +void pa_device_description_changed(pa_device *device, const char *new_description); +void pa_device_set_default_volume_control(pa_device *device, pa_volume_control *control); +void pa_device_set_default_mute_control(pa_device *device, pa_mute_control *control); + +#endif diff --git a/src/modules/volume-api/mute-control.c b/src/modules/volume-api/mute-control.c new file mode 100644 index 0000000..adc008e --- /dev/null +++ b/src/modules/volume-api/mute-control.c @@ -0,0 +1,306 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "mute-control.h" + +#include +#include +#include + +#include + +pa_mute_control *pa_mute_control_new(pa_volume_api *api, const char *name, const char *description) { + pa_mute_control *control; + + pa_assert(api); + pa_assert(name); + pa_assert(description); + + control = pa_xnew0(pa_mute_control, 1); + control->volume_api = api; + control->index = pa_volume_api_allocate_mute_control_index(api); + pa_assert_se(pa_volume_api_register_name(api, name, false, &control->name) >= 0); + control->description = pa_xstrdup(description); + control->proplist = pa_proplist_new(); + control->devices = pa_hashmap_new(NULL, NULL); + control->default_for_devices = pa_hashmap_new(NULL, NULL); + control->streams = pa_hashmap_new(NULL, NULL); + control->audio_groups = pa_hashmap_new(NULL, NULL); + + return control; +} + +void pa_mute_control_put(pa_mute_control *control, bool initial_mute, bool initial_mute_is_set, + pa_mute_control_set_initial_mute_cb_t set_initial_mute_cb) { + const char *prop_key; + void *state = NULL; + + pa_assert(control); + pa_assert(initial_mute_is_set || control->set_mute); + pa_assert(set_initial_mute_cb || !control->set_mute); + + if (initial_mute_is_set) + control->mute = initial_mute; + else + control->mute = false; + + if (set_initial_mute_cb) + set_initial_mute_cb(control); + + pa_volume_api_add_mute_control(control->volume_api, control); + + control->linked = true; + + pa_log_debug("Created mute control #%u.", control->index); + pa_log_debug(" Name: %s", control->name); + pa_log_debug(" Description: %s", control->description); + pa_log_debug(" Mute: %s", pa_yes_no(control->mute)); + pa_log_debug(" Properties:"); + + while ((prop_key = pa_proplist_iterate(control->proplist, &state))) + pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(control->proplist, prop_key))); + + pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_PUT], control); +} + +void pa_mute_control_unlink(pa_mute_control *control) { + pa_audio_group *group; + pa_device *device; + pas_stream *stream; + + pa_assert(control); + + if (control->unlinked) { + pa_log_debug("Unlinking mute control %s (already unlinked, this is a no-op).", control->name); + return; + } + + control->unlinked = true; + + pa_log_debug("Unlinking mute control %s.", control->name); + + if (control->linked) + pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK], control); + + pa_volume_api_remove_mute_control(control->volume_api, control); + + while ((group = pa_hashmap_first(control->audio_groups))) + pa_audio_group_set_mute_control(group, NULL); + + while ((stream = pa_hashmap_first(control->streams))) + pas_stream_set_mute_control(stream, NULL); + + while ((device = pa_hashmap_first(control->default_for_devices))) + pa_device_set_default_mute_control(device, NULL); + + while ((device = pa_hashmap_first(control->devices))) { + /* Why do we have this assertion here? The concern is that if we call + * pa_device_set_mute_control() for some device that has the + * use_default_mute_control flag set, then that flag will be unset as + * a side effect, and we don't want that side effect. This assertion + * should be safe, because we just called + * pa_device_set_default_mute_control(NULL) for each device that this + * control was the default for, and that should ensure that we don't + * any more hold any references to devices that used to use this + * control as the default. */ + pa_assert(!device->use_default_mute_control); + pa_device_set_mute_control(device, NULL); + } +} + +void pa_mute_control_free(pa_mute_control *control) { + pa_assert(control); + + if (!control->unlinked) + pa_mute_control_unlink(control); + + if (control->audio_groups) { + pa_assert(pa_hashmap_isempty(control->audio_groups)); + pa_hashmap_free(control->audio_groups); + } + + if (control->streams) { + pa_assert(pa_hashmap_isempty(control->streams)); + pa_hashmap_free(control->streams); + } + + if (control->default_for_devices) { + pa_assert(pa_hashmap_isempty(control->default_for_devices)); + pa_hashmap_free(control->default_for_devices); + } + + if (control->devices) { + pa_assert(pa_hashmap_isempty(control->devices)); + pa_hashmap_free(control->devices); + } + + if (control->proplist) + pa_proplist_free(control->proplist); + + pa_xfree(control->description); + + if (control->name) + pa_volume_api_unregister_name(control->volume_api, control->name); + + pa_xfree(control); +} + +void pa_mute_control_set_owner_audio_group(pa_mute_control *control, pa_audio_group *group) { + pa_assert(control); + pa_assert(group); + + control->owner_audio_group = group; +} + +static void set_mute_internal(pa_mute_control *control, bool mute) { + bool old_mute; + + pa_assert(control); + + old_mute = control->mute; + + if (mute == old_mute) + return; + + control->mute = mute; + + if (!control->linked || control->unlinked) + return; + + pa_log_debug("The mute of mute control %s changed from %s to %s.", control->name, pa_yes_no(old_mute), + pa_yes_no(control->mute)); + + pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_MUTE_CHANGED], control); +} + +int pa_mute_control_set_mute(pa_mute_control *control, bool mute) { + int r; + + pa_assert(control); + + if (!control->set_mute) { + pa_log_info("Tried to set the mute of mute control %s, but the mute control doesn't support the operation.", + control->name); + return -PA_ERR_NOTSUPPORTED; + } + + if (mute == control->mute) + return 0; + + control->set_mute_in_progress = true; + r = control->set_mute(control, mute); + control->set_mute_in_progress = false; + + if (r >= 0) + set_mute_internal(control, mute); + + return r; +} + +void pa_mute_control_description_changed(pa_mute_control *control, const char *new_description) { + char *old_description; + + pa_assert(control); + pa_assert(new_description); + + old_description = control->description; + + if (pa_streq(new_description, old_description)) + return; + + control->description = pa_xstrdup(new_description); + pa_log_debug("The description of mute control %s changed from \"%s\" to \"%s\".", control->name, old_description, + new_description); + pa_xfree(old_description); + pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_DESCRIPTION_CHANGED], control); +} + +void pa_mute_control_mute_changed(pa_mute_control *control, bool new_mute) { + pa_assert(control); + + if (!control->linked) + return; + + if (control->set_mute_in_progress) + return; + + set_mute_internal(control, new_mute); +} + +void pa_mute_control_add_device(pa_mute_control *control, pa_device *device) { + pa_assert(control); + pa_assert(device); + + pa_assert_se(pa_hashmap_put(control->devices, device, device) >= 0); +} + +void pa_mute_control_remove_device(pa_mute_control *control, pa_device *device) { + pa_assert(control); + pa_assert(device); + + pa_assert_se(pa_hashmap_remove(control->devices, device)); +} + +void pa_mute_control_add_default_for_device(pa_mute_control *control, pa_device *device) { + pa_assert(control); + pa_assert(device); + + pa_assert_se(pa_hashmap_put(control->default_for_devices, device, device) >= 0); +} + +void pa_mute_control_remove_default_for_device(pa_mute_control *control, pa_device *device) { + pa_assert(control); + pa_assert(device); + + pa_assert_se(pa_hashmap_remove(control->default_for_devices, device)); +} + +void pa_mute_control_add_stream(pa_mute_control *control, pas_stream *stream) { + pa_assert(control); + pa_assert(stream); + + pa_assert_se(pa_hashmap_put(control->streams, stream, stream) >= 0); +} + +void pa_mute_control_remove_stream(pa_mute_control *control, pas_stream *stream) { + pa_assert(control); + pa_assert(stream); + + pa_assert_se(pa_hashmap_remove(control->streams, stream)); +} + +void pa_mute_control_add_audio_group(pa_mute_control *control, pa_audio_group *group) { + pa_assert(control); + pa_assert(group); + + pa_assert_se(pa_hashmap_put(control->audio_groups, group, group) >= 0); +} + +void pa_mute_control_remove_audio_group(pa_mute_control *control, pa_audio_group *group) { + pa_assert(control); + pa_assert(group); + + pa_assert_se(pa_hashmap_remove(control->audio_groups, group)); +} diff --git a/src/modules/volume-api/mute-control.h b/src/modules/volume-api/mute-control.h new file mode 100644 index 0000000..1f70a43 --- /dev/null +++ b/src/modules/volume-api/mute-control.h @@ -0,0 +1,102 @@ +#ifndef foomutecontrolhfoo +#define foomutecontrolhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +typedef struct pa_mute_control pa_mute_control; + +struct pa_mute_control { + pa_volume_api *volume_api; + uint32_t index; + const char *name; + char *description; + pa_proplist *proplist; + bool mute; + + /* If this mute control is the "own mute control" of an audio group, this + * is set to point to that group, otherwise this is NULL. */ + pa_audio_group *owner_audio_group; + + pa_hashmap *devices; /* pa_device -> pa_device (hashmap-as-a-set) */ + pa_hashmap *default_for_devices; /* pa_device -> pa_device (hashmap-as-a-set) */ + pa_hashmap *streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ + pa_hashmap *audio_groups; /* pa_audio_group -> pa_audio_group (hashmap-as-a-set) */ + + bool linked; + bool unlinked; + bool set_mute_in_progress; + + /* Called from pa_mute_control_set_mute(). The implementation is expected + * to return a negative error code on failure. May be NULL, if the mute + * control is read-only. */ + int (*set_mute)(pa_mute_control *control, bool mute); + + void *userdata; +}; + +pa_mute_control *pa_mute_control_new(pa_volume_api *api, const char *name, const char *description); + +typedef void (*pa_mute_control_set_initial_mute_cb_t)(pa_mute_control *control); + +/* initial_mute is the preferred initial mute of the mute control + * implementation. It may be unset, if the implementation doesn't care about + * the initial state of the mute control. Read-only mute controls, however, + * must always set initial_mute. + * + * The implementation's initial mute preference may be overridden by policy, if + * the mute control isn't read-only. When the final initial mute is known, the + * the implementation is notified via set_initial_mute_cb (the mute can be read + * from control->mute). set_initial_mute_cb may be NULL, if the mute control is + * read-only. */ +void pa_mute_control_put(pa_mute_control *control, bool initial_mute, bool initial_mute_is_set, + pa_mute_control_set_initial_mute_cb_t set_initial_mute_cb); + +void pa_mute_control_unlink(pa_mute_control *control); +void pa_mute_control_free(pa_mute_control *control); + +/* Called by audio-group.c only. */ +void pa_mute_control_set_owner_audio_group(pa_mute_control *control, pa_audio_group *group); + +/* Called by clients and policy modules. */ +int pa_mute_control_set_mute(pa_mute_control *control, bool mute); + +/* Called by the mute control implementation. */ +void pa_mute_control_description_changed(pa_mute_control *control, const char *new_description); +void pa_mute_control_mute_changed(pa_mute_control *control, bool new_mute); + +/* Called from device.c only. */ +void pa_mute_control_add_device(pa_mute_control *control, pa_device *device); +void pa_mute_control_remove_device(pa_mute_control *control, pa_device *device); +void pa_mute_control_add_default_for_device(pa_mute_control *control, pa_device *device); +void pa_mute_control_remove_default_for_device(pa_mute_control *control, pa_device *device); + +/* Called from sstream.c only. */ +void pa_mute_control_add_stream(pa_mute_control *control, pas_stream *stream); +void pa_mute_control_remove_stream(pa_mute_control *control, pas_stream *stream); + +/* Called from audio-group.c only. */ +void pa_mute_control_add_audio_group(pa_mute_control *control, pa_audio_group *group); +void pa_mute_control_remove_audio_group(pa_mute_control *control, pa_audio_group *group); + +#endif diff --git a/src/modules/volume-api/sstream.c b/src/modules/volume-api/sstream.c new file mode 100644 index 0000000..e3531a8 --- /dev/null +++ b/src/modules/volume-api/sstream.c @@ -0,0 +1,366 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "sstream.h" + +#include +#include +#include +#include + +#include + +#include + +pas_stream *pas_stream_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction) { + pas_stream *stream; + + pa_assert(api); + pa_assert(name); + pa_assert(description); + + stream = pa_xnew0(pas_stream, 1); + stream->volume_api = api; + stream->index = pa_volume_api_allocate_stream_index(api); + pa_assert_se(pa_volume_api_register_name(api, name, false, &stream->name) >= 0); + stream->description = pa_xstrdup(description); + stream->direction = direction; + stream->proplist = pa_proplist_new(); + stream->use_default_volume_control = true; + stream->use_default_mute_control = true; + + return stream; +} + +static void set_volume_control_internal(pas_stream *stream, pa_volume_control *control) { + pa_volume_control *old_control; + + pa_assert(stream); + + old_control = stream->volume_control; + + if (control == old_control) + return; + + if (old_control) { + /* If the old control pointed to the own volume control of an audio + * group, then the stream's audio group for volume needs to be + * updated. We set it to NULL here, and if it should be non-NULL, that + * will be fixed very soon (a few lines down). */ + pas_stream_set_audio_group_for_volume(stream, NULL); + + pa_volume_control_remove_stream(old_control, stream); + } + + stream->volume_control = control; + + if (control) { + pa_volume_control_add_stream(control, stream); + pas_stream_set_audio_group_for_volume(stream, control->owner_audio_group); + } + + if (!stream->linked || stream->unlinked) + return; + + pa_log_debug("The volume control of stream %s changed from %s to %s.", stream->name, + old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); + + pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED], stream); +} + +static void set_mute_control_internal(pas_stream *stream, pa_mute_control *control) { + pa_mute_control *old_control; + + pa_assert(stream); + + old_control = stream->mute_control; + + if (control == old_control) + return; + + if (old_control) { + /* If the old control pointed to the own mute control of an audio + * group, then the stream's audio group for mute needs to be updated. + * We set it to NULL here, and if it should be non-NULL, that will be + * fixed very soon (a few lines down). */ + pas_stream_set_audio_group_for_mute(stream, NULL); + + pa_mute_control_remove_stream(old_control, stream); + } + + stream->mute_control = control; + + if (control) { + pa_mute_control_add_stream(control, stream); + pas_stream_set_audio_group_for_mute(stream, control->owner_audio_group); + } + + if (!stream->linked || stream->unlinked) + return; + + pa_log_debug("The mute control of stream %s changed from %s to %s.", stream->name, + old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); + + pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_MUTE_CONTROL_CHANGED], stream); +} + +void pas_stream_put(pas_stream *stream, pa_proplist *initial_properties) { + const char *prop_key; + void *state = NULL; + + pa_assert(stream); + pa_assert(!stream->create_own_volume_control || stream->delete_own_volume_control); + pa_assert(!stream->create_own_mute_control || stream->delete_own_mute_control); + + if (initial_properties) + pa_proplist_update(stream->proplist, PA_UPDATE_REPLACE, initial_properties); + + pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_VOLUME_CONTROL], stream); + + if (stream->use_default_volume_control) + set_volume_control_internal(stream, stream->own_volume_control); + + pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_MUTE_CONTROL], stream); + + if (stream->use_default_mute_control) + set_mute_control_internal(stream, stream->own_mute_control); + + pa_volume_api_add_stream(stream->volume_api, stream); + + stream->linked = true; + + pa_log_debug("Created stream #%u.", stream->index); + pa_log_debug(" Name: %s", stream->name); + pa_log_debug(" Description: %s", stream->description); + pa_log_debug(" Direction: %s", pa_direction_to_string(stream->direction)); + pa_log_debug(" Volume control: %s", stream->volume_control ? stream->volume_control->name : "(unset)"); + pa_log_debug(" Mute control: %s", stream->mute_control ? stream->mute_control->name : "(unset)"); + pa_log_debug(" Properties:"); + + while ((prop_key = pa_proplist_iterate(stream->proplist, &state))) + pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(stream->proplist, prop_key))); + + pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_PUT], stream); +} + +void pas_stream_unlink(pas_stream *stream) { + pa_assert(stream); + + if (stream->unlinked) { + pa_log_debug("Unlinking stream %s (already unlinked, this is a no-op).", stream->name); + return; + } + + stream->unlinked = true; + + pa_log_debug("Unlinking stream %s.", stream->name); + + if (stream->linked) + pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_UNLINK], stream); + + pa_volume_api_remove_stream(stream->volume_api, stream); + + pas_stream_set_audio_group_for_mute(stream, NULL); + pas_stream_set_audio_group_for_volume(stream, NULL); + pas_stream_set_mute_control(stream, NULL); + pas_stream_set_volume_control(stream, NULL); + pas_stream_set_have_own_mute_control(stream, false); + pas_stream_set_have_own_volume_control(stream, false); +} + +void pas_stream_free(pas_stream *stream) { + pa_assert(stream); + + if (!stream->unlinked) + pas_stream_unlink(stream); + + if (stream->proplist) + pa_proplist_free(stream->proplist); + + pa_xfree(stream->description); + + if (stream->name) + pa_volume_api_unregister_name(stream->volume_api, stream->name); + + pa_xfree(stream); +} + +int pas_stream_set_have_own_volume_control(pas_stream *stream, bool have) { + pa_assert(stream); + + if (have == stream->have_own_volume_control) + return 0; + + if (have) { + pa_assert(!stream->own_volume_control); + + if (!stream->create_own_volume_control) { + pa_log_debug("Stream %s doesn't support own volume control.", stream->name); + return -PA_ERR_NOTSUPPORTED; + } + + stream->own_volume_control = stream->create_own_volume_control(stream); + } else { + stream->delete_own_volume_control(stream); + stream->own_volume_control = NULL; + } + + stream->have_own_volume_control = have; + + return 0; +} + +int pas_stream_set_have_own_mute_control(pas_stream *stream, bool have) { + pa_assert(stream); + + if (have == stream->have_own_mute_control) + return 0; + + if (have) { + pa_assert(!stream->own_mute_control); + + if (!stream->create_own_mute_control) { + pa_log_debug("Stream %s doesn't support own mute control.", stream->name); + return -PA_ERR_NOTSUPPORTED; + } + + stream->own_mute_control = stream->create_own_mute_control(stream); + } else { + stream->delete_own_mute_control(stream); + stream->own_mute_control = NULL; + } + + stream->have_own_mute_control = have; + + return 0; +} + +void pas_stream_set_volume_control(pas_stream *stream, pa_volume_control *control) { + pa_assert(stream); + + stream->use_default_volume_control = false; + + if (stream->volume_control_binding) { + pa_binding_free(stream->volume_control_binding); + stream->volume_control_binding = NULL; + } + + set_volume_control_internal(stream, control); +} + +void pas_stream_set_mute_control(pas_stream *stream, pa_mute_control *control) { + pa_assert(stream); + + stream->use_default_mute_control = false; + + if (stream->mute_control_binding) { + pa_binding_free(stream->mute_control_binding); + stream->mute_control_binding = NULL; + } + + set_mute_control_internal(stream, control); +} + +void pas_stream_bind_volume_control(pas_stream *stream, pa_binding_target_info *target_info) { + pa_binding_owner_info owner_info = { + .userdata = stream, + .set_value = (pa_binding_set_value_cb_t) set_volume_control_internal, + }; + + pa_assert(stream); + pa_assert(target_info); + + stream->use_default_volume_control = false; + + if (stream->volume_control_binding) + pa_binding_free(stream->volume_control_binding); + + stream->volume_control_binding = pa_binding_new(stream->volume_api, &owner_info, target_info); +} + +void pas_stream_bind_mute_control(pas_stream *stream, pa_binding_target_info *target_info) { + pa_binding_owner_info owner_info = { + .userdata = stream, + .set_value = (pa_binding_set_value_cb_t) set_mute_control_internal, + }; + + pa_assert(stream); + pa_assert(target_info); + + stream->use_default_mute_control = false; + + if (stream->mute_control_binding) + pa_binding_free(stream->mute_control_binding); + + stream->mute_control_binding = pa_binding_new(stream->volume_api, &owner_info, target_info); +} + +void pas_stream_description_changed(pas_stream *stream, const char *new_description) { + char *old_description; + + pa_assert(stream); + pa_assert(new_description); + + old_description = stream->description; + + if (pa_streq(new_description, old_description)) + return; + + stream->description = pa_xstrdup(new_description); + pa_log_debug("The description of stream %s changed from \"%s\" to \"%s\".", stream->name, old_description, + new_description); + pa_xfree(old_description); + pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_DESCRIPTION_CHANGED], stream); +} + +void pas_stream_set_audio_group_for_volume(pas_stream *stream, pa_audio_group *group) { + pa_assert(stream); + + if (group == stream->audio_group_for_volume) + return; + + if (stream->audio_group_for_volume) + pa_audio_group_remove_volume_stream(stream->audio_group_for_volume, stream); + + stream->audio_group_for_volume = group; + + if (group) + pa_audio_group_add_volume_stream(group, stream); +} + +void pas_stream_set_audio_group_for_mute(pas_stream *stream, pa_audio_group *group) { + pa_assert(stream); + + if (group == stream->audio_group_for_mute) + return; + + if (stream->audio_group_for_mute) + pa_audio_group_remove_mute_stream(stream->audio_group_for_mute, stream); + + stream->audio_group_for_mute = group; + + if (group) + pa_audio_group_add_mute_stream(group, stream); +} diff --git a/src/modules/volume-api/sstream.h b/src/modules/volume-api/sstream.h new file mode 100644 index 0000000..a65b34c --- /dev/null +++ b/src/modules/volume-api/sstream.h @@ -0,0 +1,108 @@ +#ifndef foosstreamhfoo +#define foosstreamhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +/* We use the "pas_" prefix in pas_stream, because there's already pa_stream in + * the client API, and there's no good alternative term for streams. The 's' in + * "pas" means "server", i.e. the point is that this stuff is for servers, + * while pa_stream is for clients. */ + +typedef struct pas_stream pas_stream; + +struct pas_stream { + pa_volume_api *volume_api; + uint32_t index; + const char *name; + char *description; + pa_direction_t direction; + pa_proplist *proplist; + pa_volume_control *volume_control; + pa_mute_control *mute_control; + bool use_default_volume_control; + bool use_default_mute_control; + bool have_own_volume_control; + bool have_own_mute_control; + pa_volume_control *own_volume_control; + pa_mute_control *own_mute_control; + + pa_binding *volume_control_binding; + pa_binding *mute_control_binding; + pa_audio_group *audio_group_for_volume; + pa_audio_group *audio_group_for_mute; + + bool linked; + bool unlinked; + + /* Called when the own volume control is enabled. The callback + * implementation should return a new linked volume control object. The + * callback may be NULL, in which case the own volume control can't be + * enabled. */ + pa_volume_control *(*create_own_volume_control)(pas_stream *stream); + + /* Called when the own volume control is disabled. The implementation + * should free stream->own_volume_control. The callback may be NULL only if + * create_own_volume_control is NULL also. */ + void (*delete_own_volume_control)(pas_stream *stream); + + /* Called when the own mute control is enabled. The callback implementation + * should return a new linked mute control object. The callback may be + * NULL, in which case the own mute control can't be enabled. */ + pa_mute_control *(*create_own_mute_control)(pas_stream *stream); + + /* Called when the own mute control is disabled. The implementation should + * free stream->own_mute_control. The callback may be NULL only if + * create_own_mute_control is NULL also. */ + void (*delete_own_mute_control)(pas_stream *stream); + + void *userdata; +}; + +pas_stream *pas_stream_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction); +void pas_stream_put(pas_stream *stream, pa_proplist *initial_properties); +void pas_stream_unlink(pas_stream *stream); +void pas_stream_free(pas_stream *stream); + +/* Called by the stream implementation and possibly by policy modules. + * Enabling own controls may fail (the stream may not support own controls), + * disabling will never fail. */ +int pas_stream_set_have_own_volume_control(pas_stream *stream, bool have); +int pas_stream_set_have_own_mute_control(pas_stream *stream, bool have); + +/* Called by policy modules. */ +void pas_stream_set_volume_control(pas_stream *stream, pa_volume_control *control); +void pas_stream_set_mute_control(pas_stream *stream, pa_mute_control *control); +void pas_stream_bind_volume_control(pas_stream *stream, pa_binding_target_info *target_info); +void pas_stream_bind_mute_control(pas_stream *stream, pa_binding_target_info *target_info); + +/* Called by the stream implementation. */ +void pas_stream_description_changed(pas_stream *stream, const char *new_description); + +/* Called by audio-group.c only. Adding a stream to an audio group happens + * implicitly when the volume or mute control of a stream is set to point to + * the own control of an audio group. */ +void pas_stream_set_audio_group_for_volume(pas_stream *stream, pa_audio_group *group); +void pas_stream_set_audio_group_for_mute(pas_stream *stream, pa_audio_group *group); + +#endif diff --git a/src/modules/volume-api/stream-creator.c b/src/modules/volume-api/stream-creator.c new file mode 100644 index 0000000..2bd0053 --- /dev/null +++ b/src/modules/volume-api/stream-creator.c @@ -0,0 +1,691 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "stream-creator.h" + +#include +#include +#include + +#include +#include + +struct pa_stream_creator { + pa_volume_api *volume_api; + pa_hashmap *streams; /* pa_sink_input/pa_source_output -> struct stream */ + pa_hook_slot *sink_input_put_slot; + pa_hook_slot *sink_input_unlink_slot; + pa_hook_slot *source_output_put_slot; + pa_hook_slot *source_output_unlink_slot; +}; + +enum stream_type { + STREAM_TYPE_SINK_INPUT, + STREAM_TYPE_SOURCE_OUTPUT, +}; + +struct stream { + pa_stream_creator *creator; + enum stream_type type; + pa_sink_input *sink_input; + pa_source_output *source_output; + pa_client *client; + pas_stream *stream; + + bool unlinked; + + pa_hook_slot *proplist_changed_slot; + pa_hook_slot *client_proplist_changed_slot; + pa_hook_slot *volume_changed_slot; + pa_hook_slot *mute_changed_slot; +}; + +static char *get_stream_volume_and_mute_control_description_malloc(struct stream *stream) { + const char *application_name = NULL; + char *description; + + pa_assert(stream); + + if (stream->client) + application_name = pa_proplist_gets(stream->client->proplist, PA_PROP_APPLICATION_NAME); + + if (application_name) + description = pa_sprintf_malloc("%s: %s", application_name, stream->stream->description); + else + description = pa_xstrdup(stream->stream->description); + + return description; +} + +static int volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance) { + struct stream *stream; + pa_bvolume bvolume; + pa_cvolume cvolume; + + pa_assert(control); + pa_assert(volume); + + stream = control->userdata; + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + pa_bvolume_from_cvolume(&bvolume, &stream->sink_input->volume, &stream->sink_input->channel_map); + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + pa_bvolume_from_cvolume(&bvolume, &stream->source_output->volume, &stream->source_output->channel_map); + break; + } + + if (set_volume) + bvolume.volume = volume->volume; + + if (set_balance) + pa_bvolume_copy_balance(&bvolume, volume); + + pa_bvolume_to_cvolume(&bvolume, &cvolume); + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + pa_sink_input_set_volume(stream->sink_input, &cvolume, true, true); + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + pa_source_output_set_volume(stream->source_output, &cvolume, true, true); + break; + } + + return 0; +} + +static pa_hook_result_t sink_input_or_source_output_volume_changed_cb(void *hook_data, void *call_data, void *userdata) { + struct stream *stream = userdata; + pa_sink_input *input = NULL; + pa_source_output *output = NULL; + pa_bvolume bvolume; + + pa_assert(stream); + pa_assert(call_data); + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + input = call_data; + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + output = call_data; + break; + } + + if ((input && input != stream->sink_input) || (output && output != stream->source_output)) + return PA_HOOK_OK; + + if (input) + pa_bvolume_from_cvolume(&bvolume, &input->volume, &input->channel_map); + else + pa_bvolume_from_cvolume(&bvolume, &output->volume, &output->channel_map); + + pa_volume_control_volume_changed(stream->stream->own_volume_control, &bvolume, true, true); + + return PA_HOOK_OK; +} + +static void volume_control_set_initial_volume_cb(pa_volume_control *control) { + struct stream *stream; + pa_cvolume cvolume; + + pa_assert(control); + + stream = control->userdata; + pa_bvolume_to_cvolume(&control->volume, &cvolume); + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + pa_sink_input_set_volume(stream->sink_input, &cvolume, true, true); + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + pa_source_output_set_volume(stream->source_output, &cvolume, true, true); + break; + } +} + +static int mute_control_set_mute_cb(pa_mute_control *control, bool mute) { + struct stream *stream; + + pa_assert(control); + + stream = control->userdata; + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + pa_sink_input_set_mute(stream->sink_input, mute, true); + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + pa_source_output_set_mute(stream->source_output, mute, true); + break; + } + + return 0; +} + +static pa_hook_result_t sink_input_or_source_output_mute_changed_cb(void *hook_data, void *call_data, void *userdata) { + struct stream *stream = userdata; + pa_sink_input *input = NULL; + pa_source_output *output = NULL; + bool mute; + + pa_assert(stream); + pa_assert(call_data); + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + input = call_data; + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + output = call_data; + break; + } + + if ((input && input != stream->sink_input) || (output && output != stream->source_output)) + return PA_HOOK_OK; + + if (input) + mute = input->muted; + else + mute = output->muted; + + pa_mute_control_mute_changed(stream->stream->own_mute_control, mute); + + return PA_HOOK_OK; +} + +static void mute_control_set_initial_mute_cb(pa_mute_control *control) { + struct stream *stream; + + pa_assert(control); + + stream = control->userdata; + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + pa_sink_input_set_mute(stream->sink_input, control->mute, true); + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + pa_source_output_set_mute(stream->source_output, control->mute, true); + break; + } +} + +static const char *get_sink_input_description(pa_sink_input *input) { + const char *description; + + pa_assert(input); + + description = pa_proplist_gets(input->proplist, PA_PROP_MEDIA_NAME); + if (description) + return description; + + return NULL; +} + +static const char *get_source_output_description(pa_source_output *output) { + const char *description; + + pa_assert(output); + + description = pa_proplist_gets(output->proplist, PA_PROP_MEDIA_NAME); + if (description) + return description; + + return NULL; +} + +static pa_volume_control *stream_create_own_volume_control_cb(pas_stream *s) { + struct stream *stream; + const char *name = NULL; + char *description; + pa_volume_control *control; + pa_bvolume volume; + + pa_assert(s); + + stream = s->userdata; + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + name = "sink-input-volume-control"; + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + name = "source-output-volume-control"; + break; + } + + description = get_stream_volume_and_mute_control_description_malloc(stream); + control = pa_volume_control_new(stream->creator->volume_api, name, description, true, false); + pa_xfree(description); + control->set_volume = volume_control_set_volume_cb; + control->userdata = stream; + + pa_assert(!stream->volume_changed_slot); + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + stream->volume_changed_slot = + pa_hook_connect(&stream->sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED], PA_HOOK_NORMAL, + sink_input_or_source_output_volume_changed_cb, stream); + pa_bvolume_from_cvolume(&volume, &stream->sink_input->volume, &stream->sink_input->channel_map); + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + stream->volume_changed_slot = + pa_hook_connect(&stream->source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED], + PA_HOOK_NORMAL, sink_input_or_source_output_volume_changed_cb, stream); + pa_bvolume_from_cvolume(&volume, &stream->source_output->volume, &stream->source_output->channel_map); + break; + } + + pa_volume_control_put(control, &volume, volume_control_set_initial_volume_cb); + + return control; +} + +static void stream_delete_own_volume_control_cb(pas_stream *s) { + struct stream *stream; + + pa_assert(s); + + stream = s->userdata; + pa_hook_slot_free(stream->volume_changed_slot); + stream->volume_changed_slot = NULL; + pa_volume_control_free(s->own_volume_control); +} + +static pa_mute_control *stream_create_own_mute_control_cb(pas_stream *s) { + struct stream *stream; + const char *name = NULL; + char *description; + pa_mute_control *control; + bool mute = false; + + pa_assert(s); + + stream = s->userdata; + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + name = "sink-input-mute-control"; + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + name = "source-output-mute-control"; + break; + } + + description = get_stream_volume_and_mute_control_description_malloc(stream); + control = pa_mute_control_new(stream->creator->volume_api, name, description); + pa_xfree(description); + control->set_mute = mute_control_set_mute_cb; + control->userdata = stream; + + pa_assert(!stream->mute_changed_slot); + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + stream->mute_changed_slot = + pa_hook_connect(&stream->sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], PA_HOOK_NORMAL, + sink_input_or_source_output_mute_changed_cb, stream); + mute = stream->sink_input->muted; + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + stream->mute_changed_slot = + pa_hook_connect(&stream->source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED], + PA_HOOK_NORMAL, sink_input_or_source_output_mute_changed_cb, stream); + mute = stream->source_output->muted; + break; + } + + pa_mute_control_put(control, mute, true, mute_control_set_initial_mute_cb); + + return control; +} + +static void stream_delete_own_mute_control_cb(pas_stream *s) { + struct stream *stream; + + pa_assert(s); + + stream = s->userdata; + pa_hook_slot_free(stream->mute_changed_slot); + stream->mute_changed_slot = NULL; + pa_mute_control_free(s->own_mute_control); +} + +static pa_hook_result_t sink_input_or_source_output_proplist_changed_cb(void *hook_data, void *call_data, void *userdata) { + struct stream *stream = userdata; + pa_sink_input *input = NULL; + pa_source_output *output = NULL; + const char *new_stream_description = NULL; + char *new_control_description; + + pa_assert(stream); + pa_assert(call_data); + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + input = call_data; + + if (input != stream->sink_input) + return PA_HOOK_OK; + + new_stream_description = get_sink_input_description(input); + if (!new_stream_description) + new_stream_description = stream->stream->name; + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + output = call_data; + + if (output != stream->source_output) + return PA_HOOK_OK; + + new_stream_description = get_source_output_description(output); + if (!new_stream_description) + new_stream_description = stream->stream->name; + break; + } + + pas_stream_description_changed(stream->stream, new_stream_description); + + new_control_description = get_stream_volume_and_mute_control_description_malloc(stream); + + if (stream->stream->own_volume_control) + pa_volume_control_description_changed(stream->stream->own_volume_control, new_control_description); + + if (stream->stream->own_mute_control) + pa_mute_control_description_changed(stream->stream->own_mute_control, new_control_description); + + pa_xfree(new_control_description); + + return PA_HOOK_OK; +} + +static pa_hook_result_t client_proplist_changed_cb(void *hook_data, void *call_data, void *userdata) { + struct stream *stream = userdata; + pa_client *client = call_data; + char *description; + + pa_assert(stream); + pa_assert(client); + + if (client != stream->client) + return PA_HOOK_OK; + + description = get_stream_volume_and_mute_control_description_malloc(stream); + + if (stream->stream->own_volume_control) + pa_volume_control_description_changed(stream->stream->own_volume_control, description); + + if (stream->stream->own_mute_control) + pa_mute_control_description_changed(stream->stream->own_mute_control, description); + + pa_xfree(description); + + return PA_HOOK_OK; +} + +static struct stream *stream_new(pa_stream_creator *creator, enum stream_type type, void *core_stream) { + struct stream *stream; + const char *name = NULL; + const char *description = NULL; + pa_direction_t direction = PA_DIRECTION_OUTPUT; + + pa_assert(creator); + pa_assert(core_stream); + + stream = pa_xnew0(struct stream, 1); + stream->creator = creator; + stream->type = type; + + switch (type) { + case STREAM_TYPE_SINK_INPUT: + stream->sink_input = core_stream; + stream->client = stream->sink_input->client; + name = "sink-input-stream"; + + description = get_sink_input_description(stream->sink_input); + if (!description) + description = name; + + direction = PA_DIRECTION_OUTPUT; + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + stream->source_output = core_stream; + stream->client = stream->source_output->client; + name = "source-output-stream"; + + description = get_source_output_description(stream->source_output); + if (!description) + description = name; + + direction = PA_DIRECTION_INPUT; + break; + } + + stream->stream = pas_stream_new(creator->volume_api, name, description, direction); + stream->stream->create_own_volume_control = stream_create_own_volume_control_cb; + stream->stream->delete_own_volume_control = stream_delete_own_volume_control_cb; + stream->stream->create_own_mute_control = stream_create_own_mute_control_cb; + stream->stream->delete_own_mute_control = stream_delete_own_mute_control_cb; + stream->stream->userdata = stream; + pas_stream_set_have_own_volume_control(stream->stream, true); + pas_stream_set_have_own_mute_control(stream->stream, true); + + switch (type) { + case STREAM_TYPE_SINK_INPUT: + stream->proplist_changed_slot = + pa_hook_connect(&stream->sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_NORMAL, + sink_input_or_source_output_proplist_changed_cb, stream); + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + stream->proplist_changed_slot = + pa_hook_connect(&stream->source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], + PA_HOOK_NORMAL, sink_input_or_source_output_proplist_changed_cb, stream); + break; + } + + stream->client_proplist_changed_slot = + pa_hook_connect(&stream->creator->volume_api->core->hooks[PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED], + PA_HOOK_NORMAL, client_proplist_changed_cb, stream); + + return stream; +} + +static void stream_put(struct stream *stream) { + pa_proplist *proplist = NULL; + + pa_assert(stream); + + switch (stream->type) { + case STREAM_TYPE_SINK_INPUT: + proplist = stream->sink_input->proplist; + break; + + case STREAM_TYPE_SOURCE_OUTPUT: + proplist = stream->source_output->proplist; + break; + } + + pas_stream_put(stream->stream, proplist); +} + +static void stream_unlink(struct stream *stream) { + pa_assert(stream); + + if (stream->unlinked) + return; + + stream->unlinked = true; + + if (stream->stream) + pas_stream_unlink(stream->stream); +} + +static void stream_free(struct stream *stream) { + pa_assert(stream); + + if (!stream->unlinked) + stream_unlink(stream); + + if (stream->client_proplist_changed_slot) + pa_hook_slot_free(stream->client_proplist_changed_slot); + + if (stream->proplist_changed_slot) + pa_hook_slot_free(stream->proplist_changed_slot); + + if (stream->stream) + pas_stream_free(stream->stream); + + pa_xfree(stream); +} + +static void create_stream(pa_stream_creator *creator, enum stream_type type, void *core_stream) { + struct stream *stream; + + pa_assert(creator); + pa_assert(core_stream); + + stream = stream_new(creator, type, core_stream); + pa_hashmap_put(creator->streams, core_stream, stream); + stream_put(stream); +} + +static pa_hook_result_t sink_input_put_cb(void *hook_data, void *call_data, void *userdata) { + pa_stream_creator *creator = userdata; + pa_sink_input *input = call_data; + + pa_assert(creator); + pa_assert(input); + + create_stream(creator, STREAM_TYPE_SINK_INPUT, input); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_unlink_cb(void *hook_data, void *call_data, void *userdata) { + pa_stream_creator *creator = userdata; + pa_sink_input *input = call_data; + + pa_assert(creator); + pa_assert(input); + + pa_hashmap_remove_and_free(creator->streams, input); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_put_cb(void *hook_data, void *call_data, void *userdata) { + pa_stream_creator *creator = userdata; + pa_source_output *output = call_data; + + pa_assert(creator); + pa_assert(output); + + create_stream(creator, STREAM_TYPE_SOURCE_OUTPUT, output); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_unlink_cb(void *hook_data, void *call_data, void *userdata) { + pa_stream_creator *creator = userdata; + pa_source_output *output = call_data; + + pa_assert(creator); + pa_assert(output); + + pa_hashmap_remove_and_free(creator->streams, output); + + return PA_HOOK_OK; +} + +pa_stream_creator *pa_stream_creator_new(pa_volume_api *api) { + pa_stream_creator *creator; + uint32_t idx; + pa_sink_input *input; + pa_source_output *output; + + pa_assert(api); + + creator = pa_xnew0(pa_stream_creator, 1); + creator->volume_api = api; + creator->streams = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) stream_free); + creator->sink_input_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_NORMAL, + sink_input_put_cb, creator); + creator->sink_input_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_NORMAL, + sink_input_unlink_cb, creator); + creator->source_output_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_NORMAL, + source_output_put_cb, creator); + creator->source_output_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_NORMAL, + source_output_unlink_cb, creator); + + PA_IDXSET_FOREACH(input, api->core->sink_inputs, idx) + create_stream(creator, STREAM_TYPE_SINK_INPUT, input); + + PA_IDXSET_FOREACH(output, api->core->source_outputs, idx) + create_stream(creator, STREAM_TYPE_SOURCE_OUTPUT, output); + + return creator; +} + +void pa_stream_creator_free(pa_stream_creator *creator) { + pa_assert(creator); + + if (creator->streams) + pa_hashmap_remove_all(creator->streams); + + if (creator->source_output_unlink_slot) + pa_hook_slot_free(creator->source_output_unlink_slot); + + if (creator->source_output_put_slot) + pa_hook_slot_free(creator->source_output_put_slot); + + if (creator->sink_input_unlink_slot) + pa_hook_slot_free(creator->sink_input_unlink_slot); + + if (creator->sink_input_put_slot) + pa_hook_slot_free(creator->sink_input_put_slot); + + if (creator->streams) + pa_hashmap_free(creator->streams); + + pa_xfree(creator); +} diff --git a/src/modules/volume-api/stream-creator.h b/src/modules/volume-api/stream-creator.h new file mode 100644 index 0000000..97a03a4 --- /dev/null +++ b/src/modules/volume-api/stream-creator.h @@ -0,0 +1,32 @@ +#ifndef foostreamcreatorhfoo +#define foostreamcreatorhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +typedef struct pa_stream_creator pa_stream_creator; + +pa_stream_creator *pa_stream_creator_new(pa_volume_api *api); +void pa_stream_creator_free(pa_stream_creator *creator); + +#endif diff --git a/src/modules/volume-api/volume-api.c b/src/modules/volume-api/volume-api.c new file mode 100644 index 0000000..9abea7e --- /dev/null +++ b/src/modules/volume-api/volume-api.c @@ -0,0 +1,647 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "volume-api.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static pa_volume_api *volume_api_new(pa_core *core); +static void volume_api_free(pa_volume_api *api); + +pa_volume_api *pa_volume_api_get(pa_core *core) { + pa_volume_api *api; + + pa_assert(core); + + api = pa_shared_get(core, "volume-api"); + + if (api) + pa_volume_api_ref(api); + else { + api = volume_api_new(core); + pa_assert_se(pa_shared_set(core, "volume-api", api) >= 0); + } + + return api; +} + +pa_volume_api *pa_volume_api_ref(pa_volume_api *api) { + pa_assert(api); + + api->refcnt++; + + return api; +} + +void pa_volume_api_unref(pa_volume_api *api) { + pa_assert(api); + pa_assert(api->refcnt > 0); + + api->refcnt--; + + if (api->refcnt == 0) { + pa_assert_se(pa_shared_remove(api->core, "volume-api") >= 0); + volume_api_free(api); + } +} + +void pa_volume_api_add_binding_target_type(pa_volume_api *api, pa_binding_target_type *type) { + pa_assert(api); + pa_assert(type); + + pa_assert_se(pa_hashmap_put(api->binding_target_types, type->name, type) >= 0); + + pa_log_debug("Added binding target type %s.", type->name); + + pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_ADDED], type); +} + +void pa_volume_api_remove_binding_target_type(pa_volume_api *api, pa_binding_target_type *type) { + pa_assert(api); + pa_assert(type); + + pa_log_debug("Removing binding target type %s.", type->name); + + pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_REMOVED], type); + + pa_assert_se(pa_hashmap_remove(api->binding_target_types, type->name)); +} + +static void create_builtin_binding_target_types(pa_volume_api *api) { + pa_binding_target_type *type; + + pa_assert(api); + + type = pa_audio_group_create_binding_target_type(api); + pa_volume_api_add_binding_target_type(api, type); +} + +static void delete_builtin_binding_target_types(pa_volume_api *api) { + pa_binding_target_type *type; + + pa_assert(api); + + type = pa_hashmap_get(api->binding_target_types, PA_AUDIO_GROUP_BINDING_TARGET_TYPE); + pa_volume_api_remove_binding_target_type(api, type); +} + +static void create_objects_defer_event_cb(pa_mainloop_api *mainloop_api, pa_defer_event *event, void *userdata) { + pa_volume_api *volume_api = userdata; + + pa_assert(volume_api); + pa_assert(event == volume_api->create_objects_defer_event); + + mainloop_api->defer_free(event); + volume_api->create_objects_defer_event = NULL; + + volume_api->device_creator = pa_device_creator_new(volume_api); + volume_api->stream_creator = pa_stream_creator_new(volume_api); +} + +static pa_volume_api *volume_api_new(pa_core *core) { + pa_volume_api *api; + unsigned i; + + pa_assert(core); + + api = pa_xnew0(pa_volume_api, 1); + api->core = core; + api->refcnt = 1; + api->binding_target_types = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + api->names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree); + api->volume_controls = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + api->mute_controls = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + api->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + api->streams = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + api->audio_groups = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + for (i = 0; i < PA_VOLUME_API_HOOK_MAX; i++) + pa_hook_init(&api->hooks[i], api); + + create_builtin_binding_target_types(api); + + /* We delay the object creation to ensure that policy modules have a chance + * to affect the initialization of the objects. If we created the objects + * immediately, policy modules wouldn't have a chance of connecting to the + * object creation hooks before the objects are created. */ + api->create_objects_defer_event = core->mainloop->defer_new(core->mainloop, create_objects_defer_event_cb, api); + + pa_log_debug("Created a pa_volume_api object."); + + return api; +} + +static void volume_api_free(pa_volume_api *api) { + unsigned i; + + pa_assert(api); + pa_assert(api->refcnt == 0); + + pa_log_debug("Freeing the pa_volume_api object."); + + if (api->stream_creator) + pa_stream_creator_free(api->stream_creator); + + if (api->device_creator) + pa_device_creator_free(api->device_creator); + + if (api->create_objects_defer_event) + api->core->mainloop->defer_free(api->create_objects_defer_event); + + if (api->binding_target_types) + delete_builtin_binding_target_types(api); + + for (i = 0; i < PA_VOLUME_API_HOOK_MAX; i++) + pa_hook_done(&api->hooks[i]); + + if (api->audio_groups) { + pa_assert(pa_hashmap_isempty(api->audio_groups)); + pa_hashmap_free(api->audio_groups); + } + + if (api->streams) { + pa_assert(pa_hashmap_isempty(api->streams)); + pa_hashmap_free(api->streams); + } + + if (api->devices) { + pa_assert(pa_hashmap_isempty(api->devices)); + pa_hashmap_free(api->devices); + } + + if (api->mute_controls) { + pa_assert(pa_hashmap_isempty(api->mute_controls)); + pa_hashmap_free(api->mute_controls); + } + + if (api->volume_controls) { + pa_assert(pa_hashmap_isempty(api->volume_controls)); + pa_hashmap_free(api->volume_controls); + } + + if (api->names) { + pa_assert(pa_hashmap_isempty(api->names)); + pa_hashmap_free(api->names); + } + + if (api->binding_target_types) { + pa_assert(pa_hashmap_isempty(api->binding_target_types)); + pa_hashmap_free(api->binding_target_types); + } + + pa_xfree(api); +} + +int pa_volume_api_register_name(pa_volume_api *api, const char *requested_name, bool fail_if_already_registered, + const char **registered_name) { + char *n; + + pa_assert(api); + pa_assert(requested_name); + pa_assert(registered_name); + + n = pa_xstrdup(requested_name); + + if (pa_hashmap_put(api->names, n, n) < 0) { + unsigned i = 1; + + pa_xfree(n); + + if (fail_if_already_registered) { + pa_log("Name %s already registered.", requested_name); + return -PA_ERR_EXIST; + } + + do { + i++; + n = pa_sprintf_malloc("%s.%u", requested_name, i); + } while (pa_hashmap_put(api->names, n, n) < 0); + } + + *registered_name = n; + + return 0; +} + +void pa_volume_api_unregister_name(pa_volume_api *api, const char *name) { + pa_assert(api); + pa_assert(name); + + pa_assert_se(pa_hashmap_remove_and_free(api->names, name) >= 0); +} + +uint32_t pa_volume_api_allocate_volume_control_index(pa_volume_api *api) { + uint32_t idx; + + pa_assert(api); + + idx = api->next_volume_control_index++; + + return idx; +} + +static void set_main_output_volume_control_internal(pa_volume_api *api, pa_volume_control *control) { + pa_volume_control *old_control; + + pa_assert(api); + + old_control = api->main_output_volume_control; + + if (control == old_control) + return; + + api->main_output_volume_control = control; + pa_log_debug("Main output volume control changed from %s to %s.", old_control ? old_control->name : "(unset)", + control ? control->name : "(unset)"); + pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED], api); +} + +static void set_main_input_volume_control_internal(pa_volume_api *api, pa_volume_control *control) { + pa_volume_control *old_control; + + pa_assert(api); + + old_control = api->main_input_volume_control; + + if (control == old_control) + return; + + api->main_input_volume_control = control; + pa_log_debug("Main input volume control changed from %s to %s.", old_control ? old_control->name : "(unset)", + control ? control->name : "(unset)"); + pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_INPUT_VOLUME_CONTROL_CHANGED], api); +} + +void pa_volume_api_add_volume_control(pa_volume_api *api, pa_volume_control *control) { + pa_assert(api); + pa_assert(control); + + pa_assert_se(pa_hashmap_put(api->volume_controls, (void *) control->name, control) >= 0); +} + +int pa_volume_api_remove_volume_control(pa_volume_api *api, pa_volume_control *control) { + pa_assert(api); + pa_assert(control); + + if (!pa_hashmap_remove(api->volume_controls, control->name)) + return -1; + + if (control == api->main_output_volume_control) + set_main_output_volume_control_internal(api, NULL); + + if (control == api->main_input_volume_control) + set_main_input_volume_control_internal(api, NULL); + + return 0; +} + +pa_volume_control *pa_volume_api_get_volume_control_by_index(pa_volume_api *api, uint32_t idx) { + pa_volume_control *control; + void *state; + + pa_assert(api); + + PA_HASHMAP_FOREACH(control, api->volume_controls, state) { + if (control->index == idx) + return control; + } + + return NULL; +} + +uint32_t pa_volume_api_allocate_mute_control_index(pa_volume_api *api) { + uint32_t idx; + + pa_assert(api); + + idx = api->next_mute_control_index++; + + return idx; +} + +static void set_main_output_mute_control_internal(pa_volume_api *api, pa_mute_control *control) { + pa_mute_control *old_control; + + pa_assert(api); + + old_control = api->main_output_mute_control; + + if (control == old_control) + return; + + api->main_output_mute_control = control; + pa_log_debug("Main output mute control changed from %s to %s.", old_control ? old_control->name : "(unset)", + control ? control->name : "(unset)"); + pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_OUTPUT_MUTE_CONTROL_CHANGED], api); +} + +static void set_main_input_mute_control_internal(pa_volume_api *api, pa_mute_control *control) { + pa_mute_control *old_control; + + pa_assert(api); + + old_control = api->main_input_mute_control; + + if (control == old_control) + return; + + api->main_input_mute_control = control; + pa_log_debug("Main input mute control changed from %s to %s.", old_control ? old_control->name : "(unset)", + control ? control->name : "(unset)"); + pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_INPUT_MUTE_CONTROL_CHANGED], api); +} + +void pa_volume_api_add_mute_control(pa_volume_api *api, pa_mute_control *control) { + pa_assert(api); + pa_assert(control); + + pa_assert_se(pa_hashmap_put(api->mute_controls, (void *) control->name, control) >= 0); +} + +int pa_volume_api_remove_mute_control(pa_volume_api *api, pa_mute_control *control) { + pa_assert(api); + pa_assert(control); + + if (!pa_hashmap_remove(api->mute_controls, control->name)) + return -1; + + if (control == api->main_output_mute_control) + set_main_output_mute_control_internal(api, NULL); + + if (control == api->main_input_mute_control) + set_main_input_mute_control_internal(api, NULL); + + return 0; +} + +pa_mute_control *pa_volume_api_get_mute_control_by_index(pa_volume_api *api, uint32_t idx) { + pa_mute_control *control; + void *state; + + pa_assert(api); + + PA_HASHMAP_FOREACH(control, api->mute_controls, state) { + if (control->index == idx) + return control; + } + + return NULL; +} + +uint32_t pa_volume_api_allocate_device_index(pa_volume_api *api) { + uint32_t idx; + + pa_assert(api); + + idx = api->next_device_index++; + + return idx; +} + +void pa_volume_api_add_device(pa_volume_api *api, pa_device *device) { + pa_assert(api); + pa_assert(device); + + pa_assert_se(pa_hashmap_put(api->devices, (void *) device->name, device) >= 0); +} + +int pa_volume_api_remove_device(pa_volume_api *api, pa_device *device) { + pa_assert(api); + pa_assert(device); + + if (!pa_hashmap_remove(api->devices, device->name)) + return -1; + + return 0; +} + +pa_device *pa_volume_api_get_device_by_index(pa_volume_api *api, uint32_t idx) { + pa_device *device; + void *state; + + pa_assert(api); + + PA_HASHMAP_FOREACH(device, api->devices, state) { + if (device->index == idx) + return device; + } + + return NULL; +} + +uint32_t pa_volume_api_allocate_stream_index(pa_volume_api *api) { + uint32_t idx; + + pa_assert(api); + + idx = api->next_stream_index++; + + return idx; +} + +void pa_volume_api_add_stream(pa_volume_api *api, pas_stream *stream) { + pa_assert(api); + pa_assert(stream); + + pa_assert_se(pa_hashmap_put(api->streams, (void *) stream->name, stream) >= 0); +} + +int pa_volume_api_remove_stream(pa_volume_api *api, pas_stream *stream) { + pa_assert(api); + pa_assert(stream); + + if (!pa_hashmap_remove(api->streams, stream->name)) + return -1; + + return 0; +} + +pas_stream *pa_volume_api_get_stream_by_index(pa_volume_api *api, uint32_t idx) { + pas_stream *stream; + void *state; + + pa_assert(api); + + PA_HASHMAP_FOREACH(stream, api->streams, state) { + if (stream->index == idx) + return stream; + } + + return NULL; +} + +uint32_t pa_volume_api_allocate_audio_group_index(pa_volume_api *api) { + uint32_t idx; + + pa_assert(api); + + idx = api->next_audio_group_index++; + + return idx; +} + +void pa_volume_api_add_audio_group(pa_volume_api *api, pa_audio_group *group) { + pa_assert(api); + pa_assert(group); + + pa_assert_se(pa_hashmap_put(api->audio_groups, (void *) group->name, group) >= 0); +} + +int pa_volume_api_remove_audio_group(pa_volume_api *api, pa_audio_group *group) { + pa_assert(api); + pa_assert(group); + + if (!pa_hashmap_remove(api->audio_groups, group->name)) + return -1; + + return 0; +} + +pa_audio_group *pa_volume_api_get_audio_group_by_index(pa_volume_api *api, uint32_t idx) { + pa_audio_group *group; + void *state; + + pa_assert(api); + + PA_HASHMAP_FOREACH(group, api->audio_groups, state) { + if (group->index == idx) + return group; + } + + return NULL; +} + +void pa_volume_api_set_main_output_volume_control(pa_volume_api *api, pa_volume_control *control) { + pa_assert(api); + + if (api->main_output_volume_control_binding) { + pa_binding_free(api->main_output_volume_control_binding); + api->main_output_volume_control_binding = NULL; + } + + set_main_output_volume_control_internal(api, control); +} + +void pa_volume_api_set_main_input_volume_control(pa_volume_api *api, pa_volume_control *control) { + pa_assert(api); + + if (api->main_input_volume_control_binding) { + pa_binding_free(api->main_input_volume_control_binding); + api->main_input_volume_control_binding = NULL; + } + + set_main_input_volume_control_internal(api, control); +} + +void pa_volume_api_set_main_output_mute_control(pa_volume_api *api, pa_mute_control *control) { + pa_assert(api); + + if (api->main_output_mute_control_binding) { + pa_binding_free(api->main_output_mute_control_binding); + api->main_output_mute_control_binding = NULL; + } + + set_main_output_mute_control_internal(api, control); +} + +void pa_volume_api_set_main_input_mute_control(pa_volume_api *api, pa_mute_control *control) { + pa_assert(api); + + if (api->main_input_mute_control_binding) { + pa_binding_free(api->main_input_mute_control_binding); + api->main_input_mute_control_binding = NULL; + } + + set_main_input_mute_control_internal(api, control); +} + +void pa_volume_api_bind_main_output_volume_control(pa_volume_api *api, pa_binding_target_info *target_info) { + pa_binding_owner_info owner_info = { + .userdata = api, + .set_value = (pa_binding_set_value_cb_t) set_main_output_volume_control_internal, + }; + + pa_assert(api); + pa_assert(target_info); + + if (api->main_output_volume_control_binding) + pa_binding_free(api->main_output_volume_control_binding); + + api->main_output_volume_control_binding = pa_binding_new(api, &owner_info, target_info); +} + +void pa_volume_api_bind_main_input_volume_control(pa_volume_api *api, pa_binding_target_info *target_info) { + pa_binding_owner_info owner_info = { + .userdata = api, + .set_value = (pa_binding_set_value_cb_t) set_main_input_volume_control_internal, + }; + + pa_assert(api); + pa_assert(target_info); + + if (api->main_input_volume_control_binding) + pa_binding_free(api->main_input_volume_control_binding); + + api->main_input_volume_control_binding = pa_binding_new(api, &owner_info, target_info); +} + +void pa_volume_api_bind_main_output_mute_control(pa_volume_api *api, pa_binding_target_info *target_info) { + pa_binding_owner_info owner_info = { + .userdata = api, + .set_value = (pa_binding_set_value_cb_t) set_main_output_mute_control_internal, + }; + + pa_assert(api); + pa_assert(target_info); + + if (api->main_output_mute_control_binding) + pa_binding_free(api->main_output_mute_control_binding); + + api->main_output_mute_control_binding = pa_binding_new(api, &owner_info, target_info); +} + +void pa_volume_api_bind_main_input_mute_control(pa_volume_api *api, pa_binding_target_info *target_info) { + pa_binding_owner_info owner_info = { + .userdata = api, + .set_value = (pa_binding_set_value_cb_t) set_main_input_mute_control_internal, + }; + + pa_assert(api); + pa_assert(target_info); + + if (api->main_input_mute_control_binding) + pa_binding_free(api->main_input_mute_control_binding); + + api->main_input_mute_control_binding = pa_binding_new(api, &owner_info, target_info); +} diff --git a/src/modules/volume-api/volume-api.h b/src/modules/volume-api/volume-api.h new file mode 100644 index 0000000..73a1410 --- /dev/null +++ b/src/modules/volume-api/volume-api.h @@ -0,0 +1,163 @@ +#ifndef foovolumeapihfoo +#define foovolumeapihfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +typedef struct pa_volume_api pa_volume_api; + +/* Avoid circular dependencies... */ +typedef struct pa_audio_group pa_audio_group; +typedef struct pa_binding pa_binding; +typedef struct pa_binding_target_info pa_binding_target_info; +typedef struct pa_binding_target_type pa_binding_target_type; +typedef struct pa_device pa_device; +typedef struct pa_device_creator pa_device_creator; +typedef struct pa_mute_control pa_mute_control; +typedef struct pas_stream pas_stream; +typedef struct pa_stream_creator pa_stream_creator; +typedef struct pa_volume_control pa_volume_control; + +enum { + PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_ADDED, + PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_REMOVED, + PA_VOLUME_API_HOOK_VOLUME_CONTROL_PUT, + PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK, + PA_VOLUME_API_HOOK_VOLUME_CONTROL_DESCRIPTION_CHANGED, + PA_VOLUME_API_HOOK_VOLUME_CONTROL_VOLUME_CHANGED, + PA_VOLUME_API_HOOK_MUTE_CONTROL_PUT, + PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK, + PA_VOLUME_API_HOOK_MUTE_CONTROL_DESCRIPTION_CHANGED, + PA_VOLUME_API_HOOK_MUTE_CONTROL_MUTE_CHANGED, + PA_VOLUME_API_HOOK_DEVICE_PUT, + PA_VOLUME_API_HOOK_DEVICE_UNLINK, + PA_VOLUME_API_HOOK_DEVICE_DESCRIPTION_CHANGED, + PA_VOLUME_API_HOOK_DEVICE_VOLUME_CONTROL_CHANGED, + PA_VOLUME_API_HOOK_DEVICE_MUTE_CONTROL_CHANGED, + + /* Policy modules can use this to set the initial volume control for a + * stream. The hook callback should use pas_stream_set_volume_control() to + * set the volume control. The hook callback should not do anything if + * stream->volume_control is already non-NULL. */ + PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_VOLUME_CONTROL, + + /* Policy modules can use this to set the initial mute control for a + * stream. The hook callback should use pas_stream_set_mute_control() to + * set the mute control. The hook callback should not do anything if + * stream->mute_control is already non-NULL. */ + PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_MUTE_CONTROL, + + PA_VOLUME_API_HOOK_STREAM_PUT, + PA_VOLUME_API_HOOK_STREAM_UNLINK, + PA_VOLUME_API_HOOK_STREAM_DESCRIPTION_CHANGED, + PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED, + PA_VOLUME_API_HOOK_STREAM_MUTE_CONTROL_CHANGED, + PA_VOLUME_API_HOOK_AUDIO_GROUP_PUT, + PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK, + PA_VOLUME_API_HOOK_AUDIO_GROUP_VOLUME_CONTROL_CHANGED, + PA_VOLUME_API_HOOK_AUDIO_GROUP_MUTE_CONTROL_CHANGED, + PA_VOLUME_API_HOOK_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED, + PA_VOLUME_API_HOOK_MAIN_INPUT_VOLUME_CONTROL_CHANGED, + PA_VOLUME_API_HOOK_MAIN_OUTPUT_MUTE_CONTROL_CHANGED, + PA_VOLUME_API_HOOK_MAIN_INPUT_MUTE_CONTROL_CHANGED, + PA_VOLUME_API_HOOK_MAX +}; + +struct pa_volume_api { + pa_core *core; + unsigned refcnt; + pa_hashmap *binding_target_types; /* name -> pa_binding_target_type */ + pa_hashmap *names; /* object name -> object name (hashmap-as-a-set) */ + pa_hashmap *volume_controls; /* name -> pa_volume_control */ + pa_hashmap *mute_controls; /* name -> pa_mute_control */ + pa_hashmap *devices; /* name -> pa_device */ + pa_hashmap *streams; /* name -> pas_stream */ + pa_hashmap *audio_groups; /* name -> pa_audio_group */ + pa_volume_control *main_output_volume_control; + pa_volume_control *main_input_volume_control; + pa_mute_control *main_output_mute_control; + pa_mute_control *main_input_mute_control; + + uint32_t next_volume_control_index; + uint32_t next_mute_control_index; + uint32_t next_device_index; + uint32_t next_stream_index; + uint32_t next_audio_group_index; + pa_binding *main_output_volume_control_binding; + pa_binding *main_input_volume_control_binding; + pa_binding *main_output_mute_control_binding; + pa_binding *main_input_mute_control_binding; + pa_hook hooks[PA_VOLUME_API_HOOK_MAX]; + pa_defer_event *create_objects_defer_event; + pa_device_creator *device_creator; + pa_stream_creator *stream_creator; +}; + +pa_volume_api *pa_volume_api_get(pa_core *core); +pa_volume_api *pa_volume_api_ref(pa_volume_api *api); +void pa_volume_api_unref(pa_volume_api *api); + +void pa_volume_api_add_binding_target_type(pa_volume_api *api, pa_binding_target_type *type); +void pa_volume_api_remove_binding_target_type(pa_volume_api *api, pa_binding_target_type *type); + +/* If fail_if_already_registered is false, this function never fails. */ +int pa_volume_api_register_name(pa_volume_api *api, const char *requested_name, bool fail_if_already_registered, + const char **registered_name); + +void pa_volume_api_unregister_name(pa_volume_api *api, const char *name); + +uint32_t pa_volume_api_allocate_volume_control_index(pa_volume_api *api); +void pa_volume_api_add_volume_control(pa_volume_api *api, pa_volume_control *control); +int pa_volume_api_remove_volume_control(pa_volume_api *api, pa_volume_control *control); +pa_volume_control *pa_volume_api_get_volume_control_by_index(pa_volume_api *api, uint32_t idx); + +uint32_t pa_volume_api_allocate_mute_control_index(pa_volume_api *api); +void pa_volume_api_add_mute_control(pa_volume_api *api, pa_mute_control *control); +int pa_volume_api_remove_mute_control(pa_volume_api *api, pa_mute_control *control); +pa_mute_control *pa_volume_api_get_mute_control_by_index(pa_volume_api *api, uint32_t idx); + +uint32_t pa_volume_api_allocate_device_index(pa_volume_api *api); +void pa_volume_api_add_device(pa_volume_api *api, pa_device *device); +int pa_volume_api_remove_device(pa_volume_api *api, pa_device *device); +pa_device *pa_volume_api_get_device_by_index(pa_volume_api *api, uint32_t idx); + +uint32_t pa_volume_api_allocate_stream_index(pa_volume_api *api); +void pa_volume_api_add_stream(pa_volume_api *api, pas_stream *stream); +int pa_volume_api_remove_stream(pa_volume_api *api, pas_stream *stream); +pas_stream *pa_volume_api_get_stream_by_index(pa_volume_api *api, uint32_t idx); + +uint32_t pa_volume_api_allocate_audio_group_index(pa_volume_api *api); +void pa_volume_api_add_audio_group(pa_volume_api *api, pa_audio_group *group); +int pa_volume_api_remove_audio_group(pa_volume_api *api, pa_audio_group *group); +pa_audio_group *pa_volume_api_get_audio_group_by_index(pa_volume_api *api, uint32_t idx); + +void pa_volume_api_set_main_output_volume_control(pa_volume_api *api, pa_volume_control *control); +void pa_volume_api_set_main_input_volume_control(pa_volume_api *api, pa_volume_control *control); +void pa_volume_api_set_main_output_mute_control(pa_volume_api *api, pa_mute_control *control); +void pa_volume_api_set_main_input_mute_control(pa_volume_api *api, pa_mute_control *control); +void pa_volume_api_bind_main_output_volume_control(pa_volume_api *api, pa_binding_target_info *target_info); +void pa_volume_api_bind_main_input_volume_control(pa_volume_api *api, pa_binding_target_info *target_info); +void pa_volume_api_bind_main_output_mute_control(pa_volume_api *api, pa_binding_target_info *target_info); +void pa_volume_api_bind_main_input_mute_control(pa_volume_api *api, pa_binding_target_info *target_info); + +#endif diff --git a/src/modules/volume-api/volume-control.c b/src/modules/volume-api/volume-control.c new file mode 100644 index 0000000..c7f5dbb --- /dev/null +++ b/src/modules/volume-api/volume-control.c @@ -0,0 +1,363 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "volume-control.h" + +#include +#include +#include + +#include + +pa_volume_control *pa_volume_control_new(pa_volume_api *api, const char *name, const char *description, bool convertible_to_dB, + bool channel_map_is_writable) { + pa_volume_control *control; + + pa_assert(api); + pa_assert(name); + pa_assert(description); + + control = pa_xnew0(pa_volume_control, 1); + control->volume_api = api; + control->index = pa_volume_api_allocate_volume_control_index(api); + pa_assert_se(pa_volume_api_register_name(api, name, false, &control->name) >= 0); + control->description = pa_xstrdup(description); + control->proplist = pa_proplist_new(); + pa_bvolume_init_invalid(&control->volume); + control->convertible_to_dB = convertible_to_dB; + control->channel_map_is_writable = channel_map_is_writable; + control->devices = pa_hashmap_new(NULL, NULL); + control->default_for_devices = pa_hashmap_new(NULL, NULL); + control->streams = pa_hashmap_new(NULL, NULL); + control->audio_groups = pa_hashmap_new(NULL, NULL); + + return control; +} + +void pa_volume_control_put(pa_volume_control *control, const pa_bvolume *initial_volume, + pa_volume_control_set_initial_volume_cb_t set_initial_volume_cb) { + const char *prop_key; + void *state = NULL; + char volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX]; + char balance_str[PA_BVOLUME_SNPRINT_BALANCE_MAX]; + + pa_assert(control); + pa_assert((initial_volume && pa_bvolume_valid(initial_volume, true, true)) || control->set_volume); + pa_assert((initial_volume && pa_channel_map_valid(&initial_volume->channel_map)) || control->channel_map_is_writable); + pa_assert(set_initial_volume_cb || !control->set_volume); + + if (initial_volume && pa_bvolume_valid(initial_volume, true, false)) + control->volume.volume = initial_volume->volume; + else + control->volume.volume = PA_VOLUME_NORM / 3; + + if (initial_volume && pa_bvolume_valid(initial_volume, false, true)) + pa_bvolume_copy_balance(&control->volume, initial_volume); + else if (initial_volume && pa_channel_map_valid(&initial_volume->channel_map)) + pa_bvolume_reset_balance(&control->volume, &initial_volume->channel_map); + else { + pa_channel_map_init_mono(&control->volume.channel_map); + pa_bvolume_reset_balance(&control->volume, &control->volume.channel_map); + } + + if (set_initial_volume_cb) + set_initial_volume_cb(control); + + pa_volume_api_add_volume_control(control->volume_api, control); + + control->linked = true; + + pa_log_debug("Created volume control #%u.", control->index); + pa_log_debug(" Name: %s", control->name); + pa_log_debug(" Description: %s", control->description); + pa_log_debug(" Properties:"); + + while ((prop_key = pa_proplist_iterate(control->proplist, &state))) + pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(control->proplist, prop_key))); + + pa_log_debug(" Volume: %s", pa_volume_snprint_verbose(volume_str, sizeof(volume_str), control->volume.volume, + control->convertible_to_dB)); + pa_log_debug(" Balance: %s", pa_bvolume_snprint_balance(balance_str, sizeof(balance_str), &control->volume)); + pa_log_debug(" Channel map is writable: %s", pa_yes_no(control->channel_map_is_writable)); + + pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_PUT], control); +} + +void pa_volume_control_unlink(pa_volume_control *control) { + pa_audio_group *group; + pa_device *device; + pas_stream *stream; + + pa_assert(control); + + if (control->unlinked) { + pa_log_debug("Unlinking volume control %s (already unlinked, this is a no-op).", control->name); + return; + } + + control->unlinked = true; + + pa_log_debug("Unlinking volume control %s.", control->name); + + if (control->linked) + pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK], control); + + pa_volume_api_remove_volume_control(control->volume_api, control); + + while ((group = pa_hashmap_first(control->audio_groups))) + pa_audio_group_set_volume_control(group, NULL); + + while ((stream = pa_hashmap_first(control->streams))) + pas_stream_set_volume_control(stream, NULL); + + while ((device = pa_hashmap_first(control->default_for_devices))) + pa_device_set_default_volume_control(device, NULL); + + while ((device = pa_hashmap_first(control->devices))) { + /* Why do we have this assertion here? The concern is that if we call + * pa_device_set_volume_control() for some device that has the + * use_default_volume_control flag set, then that flag will be unset as + * a side effect, and we don't want that side effect. This assertion + * should be safe, because we just called + * pa_device_set_default_volume_control(NULL) for each device that this + * control was the default for, and that should ensure that we don't + * any more hold any references to devices that used to use this + * control as the default. */ + pa_assert(!device->use_default_volume_control); + pa_device_set_volume_control(device, NULL); + } +} + +void pa_volume_control_free(pa_volume_control *control) { + pa_assert(control); + + if (!control->unlinked) + pa_volume_control_unlink(control); + + if (control->audio_groups) { + pa_assert(pa_hashmap_isempty(control->audio_groups)); + pa_hashmap_free(control->audio_groups); + } + + if (control->streams) { + pa_assert(pa_hashmap_isempty(control->streams)); + pa_hashmap_free(control->streams); + } + + if (control->default_for_devices) { + pa_assert(pa_hashmap_isempty(control->default_for_devices)); + pa_hashmap_free(control->default_for_devices); + } + + if (control->devices) { + pa_assert(pa_hashmap_isempty(control->devices)); + pa_hashmap_free(control->devices); + } + + if (control->proplist) + pa_proplist_free(control->proplist); + + pa_xfree(control->description); + + if (control->name) + pa_volume_api_unregister_name(control->volume_api, control->name); + + pa_xfree(control); +} + +void pa_volume_control_set_owner_audio_group(pa_volume_control *control, pa_audio_group *group) { + pa_assert(control); + pa_assert(group); + + control->owner_audio_group = group; +} + +static void set_volume_internal(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance) { + pa_bvolume old_volume; + bool volume_changed; + bool balance_changed; + + pa_assert(control); + pa_assert(volume); + + old_volume = control->volume; + volume_changed = !pa_bvolume_equal(volume, &old_volume, set_volume, false); + balance_changed = !pa_bvolume_equal(volume, &old_volume, false, set_balance); + + if (!volume_changed && !balance_changed) + return; + + if (volume_changed) + control->volume.volume = volume->volume; + + if (balance_changed) + pa_bvolume_copy_balance(&control->volume, volume); + + if (!control->linked || control->unlinked) + return; + + if (volume_changed) { + char old_volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX]; + char new_volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX]; + + pa_log_debug("The volume of volume control %s changed from %s to %s.", control->name, + pa_volume_snprint_verbose(old_volume_str, sizeof(old_volume_str), old_volume.volume, + control->convertible_to_dB), + pa_volume_snprint_verbose(new_volume_str, sizeof(new_volume_str), control->volume.volume, + control->convertible_to_dB)); + } + + if (balance_changed) { + char old_balance_str[PA_BVOLUME_SNPRINT_BALANCE_MAX]; + char new_balance_str[PA_BVOLUME_SNPRINT_BALANCE_MAX]; + + pa_log_debug("The balance of volume control %s changed from %s to %s.", control->name, + pa_bvolume_snprint_balance(old_balance_str, sizeof(old_balance_str), &control->volume), + pa_bvolume_snprint_balance(new_balance_str, sizeof(new_balance_str), &control->volume)); + } + + pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_VOLUME_CHANGED], control); +} + +int pa_volume_control_set_volume(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance) { + pa_bvolume volume_local; + int r; + + pa_assert(control); + pa_assert(volume); + + volume_local = *volume; + + if (!control->set_volume) { + pa_log_info("Tried to set the volume of volume control %s, but the volume control doesn't support the operation.", + control->name); + return -PA_ERR_NOTSUPPORTED; + } + + if (set_balance + && !control->channel_map_is_writable + && !pa_channel_map_equal(&volume_local.channel_map, &control->volume.channel_map)) + pa_bvolume_remap(&volume_local, &control->volume.channel_map); + + if (pa_bvolume_equal(&volume_local, &control->volume, set_volume, set_balance)) + return 0; + + control->set_volume_in_progress = true; + r = control->set_volume(control, &volume_local, set_volume, set_balance); + control->set_volume_in_progress = false; + + if (r >= 0) + set_volume_internal(control, &volume_local, set_volume, set_balance); + + return r; +} + +void pa_volume_control_description_changed(pa_volume_control *control, const char *new_description) { + char *old_description; + + pa_assert(control); + pa_assert(new_description); + + old_description = control->description; + + if (pa_streq(new_description, old_description)) + return; + + control->description = pa_xstrdup(new_description); + pa_log_debug("The description of volume control %s changed from \"%s\" to \"%s\".", control->name, old_description, + new_description); + pa_xfree(old_description); + pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_DESCRIPTION_CHANGED], control); +} + +void pa_volume_control_volume_changed(pa_volume_control *control, const pa_bvolume *new_volume, bool volume_changed, + bool balance_changed) { + pa_assert(control); + pa_assert(new_volume); + + if (!control->linked) + return; + + if (control->set_volume_in_progress) + return; + + set_volume_internal(control, new_volume, volume_changed, balance_changed); +} + +void pa_volume_control_add_device(pa_volume_control *control, pa_device *device) { + pa_assert(control); + pa_assert(device); + + pa_assert_se(pa_hashmap_put(control->devices, device, device) >= 0); +} + +void pa_volume_control_remove_device(pa_volume_control *control, pa_device *device) { + pa_assert(control); + pa_assert(device); + + pa_assert_se(pa_hashmap_remove(control->devices, device)); +} + +void pa_volume_control_add_default_for_device(pa_volume_control *control, pa_device *device) { + pa_assert(control); + pa_assert(device); + + pa_assert_se(pa_hashmap_put(control->default_for_devices, device, device) >= 0); +} + +void pa_volume_control_remove_default_for_device(pa_volume_control *control, pa_device *device) { + pa_assert(control); + pa_assert(device); + + pa_assert_se(pa_hashmap_remove(control->default_for_devices, device)); +} + +void pa_volume_control_add_stream(pa_volume_control *control, pas_stream *stream) { + pa_assert(control); + pa_assert(stream); + + pa_assert_se(pa_hashmap_put(control->streams, stream, stream) >= 0); +} + +void pa_volume_control_remove_stream(pa_volume_control *control, pas_stream *stream) { + pa_assert(control); + pa_assert(stream); + + pa_assert_se(pa_hashmap_remove(control->streams, stream)); +} + +void pa_volume_control_add_audio_group(pa_volume_control *control, pa_audio_group *group) { + pa_assert(control); + pa_assert(group); + + pa_assert_se(pa_hashmap_put(control->audio_groups, group, group) >= 0); +} + +void pa_volume_control_remove_audio_group(pa_volume_control *control, pa_audio_group *group) { + pa_assert(control); + pa_assert(group); + + pa_assert_se(pa_hashmap_remove(control->audio_groups, group)); +} diff --git a/src/modules/volume-api/volume-control.h b/src/modules/volume-api/volume-control.h new file mode 100644 index 0000000..aaba758 --- /dev/null +++ b/src/modules/volume-api/volume-control.h @@ -0,0 +1,112 @@ +#ifndef foovolumecontrolhfoo +#define foovolumecontrolhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include +#include + +typedef struct pa_volume_control pa_volume_control; + +struct pa_volume_control { + pa_volume_api *volume_api; + uint32_t index; + const char *name; + char *description; + pa_proplist *proplist; + pa_bvolume volume; + bool convertible_to_dB; + bool channel_map_is_writable; + + /* If this volume control is the "own volume control" of an audio group, + * this is set to point to that group, otherwise this is NULL. */ + pa_audio_group *owner_audio_group; + + pa_hashmap *devices; /* pa_device -> pa_device (hashmap-as-a-set) */ + pa_hashmap *default_for_devices; /* pa_device -> pa_device (hashmap-as-a-set) */ + pa_hashmap *streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ + pa_hashmap *audio_groups; /* pa_audio_group -> pa_audio_group (hashmap-as-a-set) */ + + bool linked; + bool unlinked; + bool set_volume_in_progress; + + /* Called from pa_volume_control_set_volume(). The implementation is + * expected to return a negative error code on failure. May be NULL, if the + * volume control is read-only. */ + int (*set_volume)(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance); + + void *userdata; +}; + +pa_volume_control *pa_volume_control_new(pa_volume_api *api, const char *name, const char *description, bool convertible_to_dB, + bool channel_map_is_writable); + +typedef void (*pa_volume_control_set_initial_volume_cb_t)(pa_volume_control *control); + +/* initial_volume is the preferred initial volume of the volume control + * implementation. It may be NULL or partially invalid, if the implementation + * doesn't care about the initial state of the volume control, as long as these + * two rules are followed: + * + * 1) Read-only volume controls must always specify fully valid initial + * volume. + * 2) Volume controls with read-only channel map must always specify a valid + * channel map in initial_volume. + * + * The implementation's initial volume preference may be overridden by policy, + * if the volume control isn't read-only. When the final initial volume is + * known, the implementation is notified via set_initial_volume_cb (the volume + * can be read from control->volume). set_initial_volume_cb may be NULL, if the + * volume control is read-only. */ +void pa_volume_control_put(pa_volume_control *control, const pa_bvolume *initial_volume, + pa_volume_control_set_initial_volume_cb_t set_initial_volume_cb); + +void pa_volume_control_unlink(pa_volume_control *control); +void pa_volume_control_free(pa_volume_control *control); + +/* Called by audio-group.c only. */ +void pa_volume_control_set_owner_audio_group(pa_volume_control *control, pa_audio_group *group); + +/* Called by clients and policy modules. */ +int pa_volume_control_set_volume(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance); + +/* Called by the volume control implementation. */ +void pa_volume_control_description_changed(pa_volume_control *control, const char *new_description); +void pa_volume_control_volume_changed(pa_volume_control *control, const pa_bvolume *new_volume, bool volume_changed, + bool balance_changed); + +/* Called from device.c only. */ +void pa_volume_control_add_device(pa_volume_control *control, pa_device *device); +void pa_volume_control_remove_device(pa_volume_control *control, pa_device *device); +void pa_volume_control_add_default_for_device(pa_volume_control *control, pa_device *device); +void pa_volume_control_remove_default_for_device(pa_volume_control *control, pa_device *device); + +/* Called from sstream.c only. */ +void pa_volume_control_add_stream(pa_volume_control *control, pas_stream *stream); +void pa_volume_control_remove_stream(pa_volume_control *control, pas_stream *stream); + +/* Called from audio-group.c only. */ +void pa_volume_control_add_audio_group(pa_volume_control *control, pa_audio_group *group); +void pa_volume_control_remove_audio_group(pa_volume_control *control, pa_audio_group *group); + +#endif diff --git a/src/pulse/ext-volume-api.c b/src/pulse/ext-volume-api.c new file mode 100644 index 0000000..8e93bce --- /dev/null +++ b/src/pulse/ext-volume-api.c @@ -0,0 +1,275 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "ext-volume-api.h" + +#include +#include +#include + +#include + +int pa_ext_volume_api_balance_valid(double balance) { + return balance >= 0.0 && balance <= 1.0; +} + +int pa_ext_volume_api_bvolume_valid(const pa_ext_volume_api_bvolume *volume, int check_volume, int check_balance) { + unsigned channel; + + pa_assert(volume); + + if (check_volume && !PA_VOLUME_IS_VALID(volume->volume)) + return 0; + + if (!check_balance) + return 1; + + if (!pa_channel_map_valid(&volume->channel_map)) + return 0; + + for (channel = 0; channel < volume->channel_map.channels; channel++) { + if (!pa_ext_volume_api_balance_valid(volume->balance[channel])) + return 0; + } + + return 1; +} + +void pa_ext_volume_api_bvolume_init_invalid(pa_ext_volume_api_bvolume *volume) { + unsigned i; + + pa_assert(volume); + + volume->volume = PA_VOLUME_INVALID; + + for (i = 0; i < PA_CHANNELS_MAX; i++) + volume->balance[i] = -1.0; + + pa_channel_map_init(&volume->channel_map); +} + +void pa_ext_volume_api_bvolume_init_mono(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume) { + pa_assert(bvolume); + pa_assert(PA_VOLUME_IS_VALID(volume)); + + bvolume->volume = volume; + bvolume->balance[0] = 1.0; + pa_channel_map_init_mono(&bvolume->channel_map); +} + +int pa_ext_volume_api_bvolume_equal(const pa_ext_volume_api_bvolume *a, const pa_ext_volume_api_bvolume *b, + int check_volume, int check_balance) { + unsigned i; + + pa_assert(a); + pa_assert(b); + + if (check_volume && a->volume != b->volume) + return 0; + + if (!check_balance) + return 1; + + if (!pa_channel_map_equal(&a->channel_map, &b->channel_map)) + return 0; + + for (i = 0; i < a->channel_map.channels; i++) { + if (fabs(a->balance[i] - b->balance[i]) > 0.00001) + return 0; + } + + return 1; +} + +void pa_ext_volume_api_bvolume_from_cvolume(pa_ext_volume_api_bvolume *bvolume, const pa_cvolume *cvolume, + const pa_channel_map *map) { + unsigned i; + + pa_assert(bvolume); + pa_assert(cvolume); + pa_assert(map); + pa_assert(cvolume->channels == map->channels); + + bvolume->volume = pa_cvolume_max(cvolume); + bvolume->channel_map = *map; + + for (i = 0; i < map->channels; i++) { + if (bvolume->volume != PA_VOLUME_MUTED) + bvolume->balance[i] = ((double) cvolume->values[i]) / ((double) bvolume->volume); + else + bvolume->balance[i] = 1.0; + } +} + +void pa_ext_volume_api_bvolume_to_cvolume(const pa_ext_volume_api_bvolume *bvolume, pa_cvolume *cvolume) { + unsigned i; + + pa_assert(bvolume); + pa_assert(cvolume); + pa_assert(pa_ext_volume_api_bvolume_valid(bvolume, true, true)); + + cvolume->channels = bvolume->channel_map.channels; + + for (i = 0; i < bvolume->channel_map.channels; i++) + cvolume->values[i] = bvolume->volume * bvolume->balance[i]; +} + +void pa_ext_volume_api_bvolume_copy_balance(pa_ext_volume_api_bvolume *to, + const pa_ext_volume_api_bvolume *from) { + pa_assert(to); + pa_assert(from); + + memcpy(to->balance, from->balance, sizeof(from->balance)); + to->channel_map = from->channel_map; +} + +void pa_ext_volume_api_bvolume_reset_balance(pa_ext_volume_api_bvolume *volume, const pa_channel_map *map) { + unsigned i; + + pa_assert(volume); + pa_assert(map); + pa_assert(pa_channel_map_valid(map)); + + for (i = 0; i < map->channels; i++) + volume->balance[i] = 1.0; + + volume->channel_map = *map; +} + +void pa_ext_volume_api_bvolume_remap(pa_ext_volume_api_bvolume *volume, const pa_channel_map *to) { + unsigned i; + pa_cvolume cvolume; + + pa_assert(volume); + pa_assert(to); + pa_assert(pa_ext_volume_api_bvolume_valid(volume, false, true)); + pa_assert(pa_channel_map_valid(to)); + + cvolume.channels = volume->channel_map.channels; + + for (i = 0; i < cvolume.channels; i++) + cvolume.values[i] = volume->balance[i] * (double) PA_VOLUME_NORM; + + pa_cvolume_remap(&cvolume, &volume->channel_map, to); + + for (i = 0; i < to->channels; i++) + volume->balance[i] = (double) cvolume.values[i] / (double) PA_VOLUME_NORM; + + volume->channel_map = *to; +} + +double pa_ext_volume_api_bvolume_get_left_right_balance(const pa_ext_volume_api_bvolume *volume) { + pa_ext_volume_api_bvolume bvolume; + pa_cvolume cvolume; + double ret; + + pa_assert(volume); + + bvolume.volume = PA_VOLUME_NORM; + pa_ext_volume_api_bvolume_copy_balance(&bvolume, volume); + pa_ext_volume_api_bvolume_to_cvolume(&bvolume, &cvolume); + ret = pa_cvolume_get_balance(&cvolume, &volume->channel_map); + + return ret; +} + +void pa_ext_volume_api_bvolume_set_left_right_balance(pa_ext_volume_api_bvolume *volume, double balance) { + pa_cvolume cvolume; + pa_volume_t old_volume; + + pa_assert(volume); + + if (!pa_channel_map_can_balance(&volume->channel_map)) + return; + + pa_cvolume_reset(&cvolume, volume->channel_map.channels); + pa_cvolume_set_balance(&cvolume, &volume->channel_map, balance); + old_volume = volume->volume; + pa_ext_volume_api_bvolume_from_cvolume(volume, &cvolume, &volume->channel_map); + volume->volume = old_volume; +} + +double pa_ext_volume_api_bvolume_get_rear_front_balance(const pa_ext_volume_api_bvolume *volume) { + pa_ext_volume_api_bvolume bvolume; + pa_cvolume cvolume; + double ret; + + pa_assert(volume); + + bvolume.volume = PA_VOLUME_NORM; + pa_ext_volume_api_bvolume_copy_balance(&bvolume, volume); + pa_ext_volume_api_bvolume_to_cvolume(&bvolume, &cvolume); + ret = pa_cvolume_get_fade(&cvolume, &volume->channel_map); + + return ret; +} + +void pa_ext_volume_api_bvolume_set_rear_front_balance(pa_ext_volume_api_bvolume *volume, double balance) { + pa_cvolume cvolume; + pa_volume_t old_volume; + + pa_assert(volume); + + if (!pa_channel_map_can_fade(&volume->channel_map)) + return; + + pa_cvolume_reset(&cvolume, volume->channel_map.channels); + pa_cvolume_set_fade(&cvolume, &volume->channel_map, balance); + old_volume = volume->volume; + pa_ext_volume_api_bvolume_from_cvolume(volume, &cvolume, &volume->channel_map); + volume->volume = old_volume; +} + +char *pa_ext_volume_api_bvolume_snprint_balance(char *buf, size_t buf_len, + const pa_ext_volume_api_bvolume *volume) { + char *e; + unsigned channel; + bool first = true; + + pa_assert(buf); + pa_assert(buf_len > 0); + pa_assert(volume); + + pa_init_i18n(); + + if (!pa_ext_volume_api_bvolume_valid(volume, true, true)) { + pa_snprintf(buf, buf_len, _("(invalid)")); + return buf; + } + + *(e = buf) = 0; + + for (channel = 0; channel < volume->channel_map.channels && buf_len > 1; channel++) { + buf_len -= pa_snprintf(e, buf_len, "%s%s: %u%%", + first ? "" : ", ", + pa_channel_position_to_string(volume->channel_map.map[channel]), + (unsigned) (volume->balance[channel] * 100 + 0.5)); + + e = strchr(e, 0); + first = false; + } + + return buf; +} diff --git a/src/pulse/ext-volume-api.h b/src/pulse/ext-volume-api.h new file mode 100644 index 0000000..36b7748 --- /dev/null +++ b/src/pulse/ext-volume-api.h @@ -0,0 +1,68 @@ +#ifndef fooextvolumeapihfoo +#define fooextvolumeapihfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include +#include +#include + +/* This API is temporary, and has no stability guarantees whatsoever. Think + * twice before making anything that relies on this API. This is undocumented + * for a reason. */ + +PA_C_DECL_BEGIN + +typedef struct pa_ext_volume_api_bvolume pa_ext_volume_api_bvolume; + +struct pa_ext_volume_api_bvolume { + pa_volume_t volume; + double balance[PA_CHANNELS_MAX]; + pa_channel_map channel_map; +}; + +int pa_ext_volume_api_balance_valid(double balance) PA_GCC_CONST; +int pa_ext_volume_api_bvolume_valid(const pa_ext_volume_api_bvolume *volume, int check_volume, int check_balance) + PA_GCC_PURE; +void pa_ext_volume_api_bvolume_init_invalid(pa_ext_volume_api_bvolume *volume); +void pa_ext_volume_api_bvolume_init_mono(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume); +int pa_ext_volume_api_bvolume_equal(const pa_ext_volume_api_bvolume *a, const pa_ext_volume_api_bvolume *b, + int check_volume, int check_balance) PA_GCC_PURE; +void pa_ext_volume_api_bvolume_from_cvolume(pa_ext_volume_api_bvolume *bvolume, const pa_cvolume *cvolume, + const pa_channel_map *map); +void pa_ext_volume_api_bvolume_to_cvolume(const pa_ext_volume_api_bvolume *bvolume, pa_cvolume *cvolume); +void pa_ext_volume_api_bvolume_copy_balance(pa_ext_volume_api_bvolume *to, + const pa_ext_volume_api_bvolume *from); +void pa_ext_volume_api_bvolume_reset_balance(pa_ext_volume_api_bvolume *volume, const pa_channel_map *map); +void pa_ext_volume_api_bvolume_remap(pa_ext_volume_api_bvolume *volume, const pa_channel_map *to); +double pa_ext_volume_api_bvolume_get_left_right_balance(const pa_ext_volume_api_bvolume *volume) PA_GCC_PURE; +void pa_ext_volume_api_bvolume_set_left_right_balance(pa_ext_volume_api_bvolume *volume, double balance); +double pa_ext_volume_api_bvolume_get_rear_front_balance(const pa_ext_volume_api_bvolume *volume) PA_GCC_PURE; +void pa_ext_volume_api_bvolume_set_rear_front_balance(pa_ext_volume_api_bvolume *volume, double balance); + +#define PA_EXT_VOLUME_API_BVOLUME_SNPRINT_BALANCE_MAX 500 +char *pa_ext_volume_api_bvolume_snprint_balance(char *buf, size_t buf_size, + const pa_ext_volume_api_bvolume *volume); + +PA_C_DECL_END + +#endif -- 2.1.4 --- a/po/POTFILES.in 2016-04-13 17:40:00.818008672 +0200 +++ b/po/POTFILES.in 2016-04-13 17:40:30.885008622 +0200 @@ -198,3 +198,5 @@ src/utils/padsp.c src/utils/pasuspender.c src/utils/pax11publish.c +src/modules/volume-api/device-creator.c +src/pulse/ext-volume-api.c