volume-api: Add libvolume-api.so 66/21966/1
authorTanu Kaskinen <tanu.kaskinen@linux.intel.com>
Wed, 21 May 2014 08:51:27 +0000 (11:51 +0300)
committerIsmo Puustinen <ismo.puustinen@intel.com>
Wed, 28 May 2014 09:40:04 +0000 (12:40 +0300)
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 <jaska.uimonen@intel.com>
24 files changed:
Makefile.am
src/Makefile.am
src/map-file
src/modules/volume-api/audio-group.c [new file with mode: 0644]
src/modules/volume-api/audio-group.h [new file with mode: 0644]
src/modules/volume-api/binding.c [new file with mode: 0644]
src/modules/volume-api/binding.h [new file with mode: 0644]
src/modules/volume-api/bvolume.h [new file with mode: 0644]
src/modules/volume-api/device-creator.c [new file with mode: 0644]
src/modules/volume-api/device-creator.h [new file with mode: 0644]
src/modules/volume-api/device.c [new file with mode: 0644]
src/modules/volume-api/device.h [new file with mode: 0644]
src/modules/volume-api/mute-control.c [new file with mode: 0644]
src/modules/volume-api/mute-control.h [new file with mode: 0644]
src/modules/volume-api/sstream.c [new file with mode: 0644]
src/modules/volume-api/sstream.h [new file with mode: 0644]
src/modules/volume-api/stream-creator.c [new file with mode: 0644]
src/modules/volume-api/stream-creator.h [new file with mode: 0644]
src/modules/volume-api/volume-api.c [new file with mode: 0644]
src/modules/volume-api/volume-api.h [new file with mode: 0644]
src/modules/volume-api/volume-control.c [new file with mode: 0644]
src/modules/volume-api/volume-control.h [new file with mode: 0644]
src/pulse/ext-volume-api.c [new file with mode: 0644]
src/pulse/ext-volume-api.h [new file with mode: 0644]

index 9431d4a..cf4a648 100644 (file)
@@ -58,6 +58,9 @@ moduledevdir   = $(includedir)/pulsemodule/pulsecore
 moduledevinternal_DATA = src/pulse/internal.h src/pulse/client-conf.h src/pulse/fork-detect.h
 moduledevinternaldir   = $(includedir)/pulsemodule/pulse
 
+moduledevvolumeapi_DATA = src/modules/volume-api/*.h
+moduledevvolumeapidir   = $(includedir)/pulsemodule/modules/volume-api
+
 filterdir = /etc/pulse/filter
 filter_DATA = filter/filter_44100_48000.dat \
              filter/filter_44100_8000.dat \
index 22b9b81..bc41dca 100644 (file)
@@ -765,6 +765,7 @@ pulseinclude_HEADERS = \
                pulse/ext-stream-restore.h \
                pulse/ext-echo-cancel.h \
                pulse/ext-node-manager.h \
+               pulse/ext-volume-api.h \
                pulse/format.h \
                pulse/gccmacro.h \
                pulse/introspect.h \
@@ -819,6 +820,7 @@ libpulse_la_SOURCES = \
                pulse/ext-stream-restore.c pulse/ext-stream-restore.h \
                pulse/ext-echo-cancel.c pulse/ext-echo-cancel.h \
                pulse/ext-node-manager.c pulse/ext-node-manager.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
index fbf3f22..1f64a2f 100644 (file)
@@ -189,6 +189,21 @@ pa_ext_node_manager_subscribe;
 pa_ext_node_manager_set_subscribe_cb;
 pa_ext_echo_cancel_set_volume;
 pa_ext_echo_cancel_set_device;
+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 (file)
index 0000000..76bfa69
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include "audio-group.h"
+
+#include <modules/volume-api/sstream.h>
+
+#include <pulsecore/core-util.h>
+
+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 (file)
index 0000000..41591ba
--- /dev/null
@@ -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 <modules/volume-api/binding.h>
+#include <modules/volume-api/mute-control.h>
+#include <modules/volume-api/volume-control.h>
+
+#include <pulse/proplist.h>
+
+#include <inttypes.h>
+
+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 (file)
index 0000000..6e73119
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include "binding.h"
+
+#include <pulse/def.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+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 (file)
index 0000000..ba4dea8
--- /dev/null
@@ -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 <modules/volume-api/volume-api.h>
+
+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 (file)
index 0000000..0317fb6
--- /dev/null
@@ -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 <pulse/ext-volume-api.h>
+
+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 (file)
index 0000000..1d912ba
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include "device-creator.h"
+
+#include <modules/volume-api/device.h>
+#include <modules/volume-api/mute-control.h>
+#include <modules/volume-api/volume-control.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/i18n.h>
+
+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 (file)
index 0000000..bcec8d9
--- /dev/null
@@ -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 <modules/volume-api/volume-api.h>
+
+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 (file)
index 0000000..ea496ba
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include "device.h"
+
+#include <modules/volume-api/mute-control.h>
+#include <modules/volume-api/volume-control.h>
+
+#include <pulse/direction.h>
+
+#include <pulsecore/core-util.h>
+
+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 (file)
index 0000000..9eac7e9
--- /dev/null
@@ -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 <modules/volume-api/volume-api.h>
+
+#include <pulsecore/dynarray.h>
+
+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 (file)
index 0000000..adc008e
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include "mute-control.h"
+
+#include <modules/volume-api/audio-group.h>
+#include <modules/volume-api/device.h>
+#include <modules/volume-api/sstream.h>
+
+#include <pulsecore/core-util.h>
+
+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 (file)
index 0000000..1f70a43
--- /dev/null
@@ -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 <modules/volume-api/volume-api.h>
+
+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 (file)
index 0000000..e3531a8
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include "sstream.h"
+
+#include <modules/volume-api/audio-group.h>
+#include <modules/volume-api/binding.h>
+#include <modules/volume-api/mute-control.h>
+#include <modules/volume-api/volume-control.h>
+
+#include <pulse/direction.h>
+
+#include <pulsecore/core-util.h>
+
+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 (file)
index 0000000..a65b34c
--- /dev/null
@@ -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 <modules/volume-api/volume-api.h>
+
+/* 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 (file)
index 0000000..2bd0053
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include "stream-creator.h"
+
+#include <modules/volume-api/sstream.h>
+#include <modules/volume-api/mute-control.h>
+#include <modules/volume-api/volume-control.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/i18n.h>
+
+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 (file)
index 0000000..97a03a4
--- /dev/null
@@ -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 <modules/volume-api/volume-api.h>
+
+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 (file)
index 0000000..9abea7e
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include "volume-api.h"
+
+#include <modules/volume-api/audio-group.h>
+#include <modules/volume-api/binding.h>
+#include <modules/volume-api/device.h>
+#include <modules/volume-api/device-creator.h>
+#include <modules/volume-api/sstream.h>
+#include <modules/volume-api/stream-creator.h>
+#include <modules/volume-api/volume-control.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/shared.h>
+
+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 (file)
index 0000000..73a1410
--- /dev/null
@@ -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 <pulsecore/core.h>
+
+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 (file)
index 0000000..c7f5dbb
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include "volume-control.h"
+
+#include <modules/volume-api/audio-group.h>
+#include <modules/volume-api/device.h>
+#include <modules/volume-api/sstream.h>
+
+#include <pulsecore/core-util.h>
+
+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 (file)
index 0000000..aaba758
--- /dev/null
@@ -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 <modules/volume-api/bvolume.h>
+#include <modules/volume-api/volume-api.h>
+
+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 (file)
index 0000000..8e93bce
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include "ext-volume-api.h"
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/i18n.h>
+#include <pulsecore/macro.h>
+
+#include <math.h>
+
+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 (file)
index 0000000..36b7748
--- /dev/null
@@ -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 <pulse/cdecl.h>
+#include <pulse/context.h>
+#include <pulse/volume.h>
+
+/* 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