Fix pulseaudio. 38/27138/1
authorRonan Le Martret <ronan@fridu.net>
Fri, 5 Sep 2014 07:58:03 +0000 (09:58 +0200)
committerRonan Le Martret <ronan@fridu.net>
Fri, 5 Sep 2014 08:11:48 +0000 (10:11 +0200)
Change-Id: Ibc8e7d6c261df47e722d16858d6dae348ada42c3
Signed-off-by: Ronan Le Martret <ronan@fridu.net>
105 files changed:
recipes-multimedia/pulseaudio/pulseaudio_5.0.bbappend
recipes-multimedia/pulseaudio/pulseaudio_5.0/0001-volume-ramp-additions-to-the-low-level-infra.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0002-volume-ramp-add-volume-ramping-to-sink.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0003-volume-ramp-adding-volume-ramping-to-sink-input.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0004-build-sys-install-files-for-a-module-development.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0005-jack-detection-fix-for-wired-headset.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0006-make-pa_thread_mq_done-safe-for-subsequent-calls.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0007-node-manager-adding-external-node-manager-API.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0008-node-manager-adding-node-support-for-pactl.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0009-add-internal-corking-state-for-sink-input.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0010-bluetooth-Add-basic-support-for-HEADSET-profiles.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0011-bluetooth-Create-Handsfree-Audio-Agent-NULL-backend.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0012-bluetooth-Create-Handsfree-Audio-Agent-oFono-backend.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0013-bluetooth-Monitor-D-Bus-signals.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0014-bluetooth-Create-pa_bluetooth_dbus_send_and_add_to_p.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0015-bluetooth-Register-Unregister-Handsfree-Audio-Agent-.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0016-bluetooth-List-HandsfreeAudioCard-objects-from-oFono.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0017-bluetooth-Parse-HandsfreeAudioCard-properties.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0018-bluetooth-Implement-transport-acquire-for-hf_audio_a.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0019-bluetooth-Track-oFono-service.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0020-bluetooth-Handle-CardAdded-signal.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0021-bluetooth-Handle-CardRemoved-signal.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0022-bluetooth-Implement-org.ofono.HandsfreeAudioAgent.Ne.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0023-bluetooth-Fix-not-handle-fd-in-DEFER_SETUP-state.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0024-bluetooth-Suspend-sink-source-on-HFP-s-stream-HUP.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0025-bluetooth-Implement-transport-release-for-hf_audio_a.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0026-bluetooth-Fixes-HFP-audio-transfer-when-initiator.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0027-bluetooth-Set-off-profile-as-default-for-newly-creat.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0028-fix-ofono-and-pulseaudio-starting-order-assert.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0029-hfp-do-safe-strcmp-in-dbus-handler.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0030-add-parameter-to-define-key-used-in-stream-restore.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0031-increase-alsa-rewind-safeguard.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0032-fix-for-profile-change-prototype-in-bluez5-patch.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0033-changes-to-pa-simple-api-samsung.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0034-add-support-for-samsung-power-management-samsung.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0035-Add-preload-fileter-for-resample-samsung.patch.gz [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0036-Enhance-for-echo-cancel-samsung.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0037-add-support-for-dlog-samsung.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0038-add-policy-module-samsung.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0039-add-bluetooth-a2dp-aptx-codec-support-samsung.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0040-create-pa_ready-file-samsung.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0041-set-alsa-suspend-timeout-to-zero-samsung.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0042-cope-with-possible-infinite-waiting-in-startup-samsu.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0043-use-udev-only-for-usb-devices-samsung.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0044-fixes-and-improvements-to-makefile-and-configure-in-.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0045-fix-warning-in-gconf-helper.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0046-volume-ramp-add-client-api-support-for-volume-rampin.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0047-adjust-default-bluetooth-profile-to-off.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0048-Add-bt_profile_set-patch-which-fixed-bt-a2dp-hsp-pro.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0049-added-pulseaudio.service-file.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0050-.gitignore-Add-pulsecore-config.h.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0051-pactl-Fix-crash-with-older-servers.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0052-volume-Increase-PA_SW_VOLUME_SNPRINT_DB_MAX.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0053-sink-input-source-output-Fix-mute-saving.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0054-direction-Add-a-couple-of-direction-helper-functions.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0055-core-util-Add-pa_join.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0056-dynarray-Add-pa_dynarray_get_raw_array.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0057-dynarray-Add-PA_DYNARRAY_FOREACH.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0058-dynarray-Add-pa_dynarray_remove_last.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0059-dynarray-Add-pa_dynarray_remove_all.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0060-hashmap-Add-pa_hashmap_remove_and_free.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0061-device-port-Add-pa_device_port.active.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0062-sink-source-Assign-to-reference_volume-from-only-one.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0063-sink-input-source-output-Assign-to-volume-from-only-.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0064-sink-source-Return-early-from-set_mute.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0065-sink-input-source-output-Add-logging-to-set_mute.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0066-sink-source-Allow-calling-set_mute-during-initializa.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0067-echo-cancel-Remove-redundant-get_mute-callback.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0068-sink-source-Call-set_mute-from-mute_changed.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0069-sink-source-Assign-to-s-muted-from-only-one-place.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0070-sink-input-source-output-Remove-redundant-get_mute-f.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0071-solaris-tunnel-Remove-some-redundant-boolean-convers.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0072-sink-source-Add-hooks-for-volume-changes.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0073-sink-input-source-output-Add-hooks-for-volume-change.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0074-sink-source-Add-hooks-for-mute-changes.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0075-sink-input-source-output-Add-hooks-for-mute-changes.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0076-sink-Link-monitor-source-before-activating-port.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0077-context-extension-Add-the-pa_extension-class.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0078-volume-api-Add-libvolume-api.so.patch.gz [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0079-Add-module-volume-api-and-the-related-client-API.patch.gz [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0080-pactl-Add-support-for-the-new-volume-API.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0081-Add-module-audio-groups.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0082-Add-module-main-volume-policy.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0083-configuration-Add-default-IVI-audio-group-and-main-v.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0084-sink-source-Initialize-port-before-fixate-hook-fixes.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0085-configuration-pulseaudio-tizen-configuration-in-defa.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0086-pactl-Fix-crash-in-pactl-list.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0087-configuration-x-example-x-tizen-ivi-in-volume-config.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0088-device-creator-stream-creator-Add-a-couple-of-assert.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0089-main-volume-policy-Fix-a-memory-leak.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0090-audio-groups-fix-issues-found-by-static-analysis.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0091-core-util-Add-pa_append_to_home_dir.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0092-core-util-Add-pa_get_config_home_dir.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0093-core-util-Add-pa_append_to_config_home_dir.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0094-core-Create-the-config-home-directory-on-startup.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0095-alsa-Handle-unlinking-of-uninitialized-streams-grace.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0096-role-cork-Handle-unlinking-of-uninitialized-streams-.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0097-role-ducking-Handle-unlinking-of-uninitialized-strea.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0098-core-util-Add-pa_boolean_to_string.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0099-sink-input-source-output-Assign-to-reference_ratio-f.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0100-sink-input-source-output-Add-hooks-for-reference-rat.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0101-sink-input-source-output-Use-new_data.volume-only-fo.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0102-sink-input-source-output-Add-the-real-object-pointer.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_5.0/0103-audio-groups-main-volume-policy-volume-api-Various-f.patch [new file with mode: 0644]
recipes-multimedia/pulseaudio/pulseaudio_git.bb_old [new file with mode: 0644]

index 1c2694a..017b273 100644 (file)
@@ -1,4 +1,110 @@
 FILESEXTRAPATHS_prepend := "${THISDIR}/pulseaudio_5.0"
 
 SRC_URI += "file://change_libsystemd_to_libsystemd-login_in_configure.patch"
-SRC_URI += "file://changes-to-pa-simple-api-samsung.patch"
+#SRC_URI += "file://changes-to-pa-simple-api-samsung.patch"
+
+B="${S}"
+
+SRC_URI += "file://0001-volume-ramp-additions-to-the-low-level-infra.patch"
+SRC_URI += "file://0002-volume-ramp-add-volume-ramping-to-sink.patch"
+SRC_URI += "file://0003-volume-ramp-adding-volume-ramping-to-sink-input.patch"
+SRC_URI += "file://0004-build-sys-install-files-for-a-module-development.patch"
+SRC_URI += "file://0005-jack-detection-fix-for-wired-headset.patch"
+SRC_URI += "file://0006-make-pa_thread_mq_done-safe-for-subsequent-calls.patch"
+SRC_URI += "file://0007-node-manager-adding-external-node-manager-API.patch"
+SRC_URI += "file://0008-node-manager-adding-node-support-for-pactl.patch"
+SRC_URI += "file://0009-add-internal-corking-state-for-sink-input.patch"
+SRC_URI += "file://0010-bluetooth-Add-basic-support-for-HEADSET-profiles.patch"
+SRC_URI += "file://0011-bluetooth-Create-Handsfree-Audio-Agent-NULL-backend.patch"
+SRC_URI += "file://0012-bluetooth-Create-Handsfree-Audio-Agent-oFono-backend.patch"
+SRC_URI += "file://0013-bluetooth-Monitor-D-Bus-signals.patch"
+SRC_URI += "file://0014-bluetooth-Create-pa_bluetooth_dbus_send_and_add_to_p.patch"
+SRC_URI += "file://0015-bluetooth-Register-Unregister-Handsfree-Audio-Agent-.patch"
+SRC_URI += "file://0016-bluetooth-List-HandsfreeAudioCard-objects-from-oFono.patch"
+SRC_URI += "file://0017-bluetooth-Parse-HandsfreeAudioCard-properties.patch"
+SRC_URI += "file://0018-bluetooth-Implement-transport-acquire-for-hf_audio_a.patch"
+SRC_URI += "file://0019-bluetooth-Track-oFono-service.patch"
+SRC_URI += "file://0020-bluetooth-Handle-CardAdded-signal.patch"
+SRC_URI += "file://0021-bluetooth-Handle-CardRemoved-signal.patch"
+SRC_URI += "file://0022-bluetooth-Implement-org.ofono.HandsfreeAudioAgent.Ne.patch"
+SRC_URI += "file://0023-bluetooth-Fix-not-handle-fd-in-DEFER_SETUP-state.patch"
+SRC_URI += "file://0024-bluetooth-Suspend-sink-source-on-HFP-s-stream-HUP.patch"
+SRC_URI += "file://0025-bluetooth-Implement-transport-release-for-hf_audio_a.patch"
+SRC_URI += "file://0026-bluetooth-Fixes-HFP-audio-transfer-when-initiator.patch"
+SRC_URI += "file://0027-bluetooth-Set-off-profile-as-default-for-newly-creat.patch"
+SRC_URI += "file://0028-fix-ofono-and-pulseaudio-starting-order-assert.patch"
+SRC_URI += "file://0029-hfp-do-safe-strcmp-in-dbus-handler.patch"
+SRC_URI += "file://0030-add-parameter-to-define-key-used-in-stream-restore.patch"
+SRC_URI += "file://0031-increase-alsa-rewind-safeguard.patch"
+SRC_URI += "file://0032-fix-for-profile-change-prototype-in-bluez5-patch.patch"
+SRC_URI += "file://0033-changes-to-pa-simple-api-samsung.patch"
+SRC_URI += "file://0034-add-support-for-samsung-power-management-samsung.patch"
+SRC_URI += "file://0035-Add-preload-fileter-for-resample-samsung.patch.gz"
+SRC_URI += "file://0036-Enhance-for-echo-cancel-samsung.patch"
+SRC_URI += "file://0037-add-support-for-dlog-samsung.patch"
+SRC_URI += "file://0038-add-policy-module-samsung.patch"
+SRC_URI += "file://0039-add-bluetooth-a2dp-aptx-codec-support-samsung.patch"
+SRC_URI += "file://0040-create-pa_ready-file-samsung.patch"
+SRC_URI += "file://0041-set-alsa-suspend-timeout-to-zero-samsung.patch"
+SRC_URI += "file://0042-cope-with-possible-infinite-waiting-in-startup-samsu.patch"
+SRC_URI += "file://0043-use-udev-only-for-usb-devices-samsung.patch"
+SRC_URI += "file://0044-fixes-and-improvements-to-makefile-and-configure-in-.patch"
+SRC_URI += "file://0045-fix-warning-in-gconf-helper.patch"
+SRC_URI += "file://0046-volume-ramp-add-client-api-support-for-volume-rampin.patch"
+SRC_URI += "file://0047-adjust-default-bluetooth-profile-to-off.patch"
+SRC_URI += "file://0048-Add-bt_profile_set-patch-which-fixed-bt-a2dp-hsp-pro.patch"
+SRC_URI += "file://0049-added-pulseaudio.service-file.patch"
+SRC_URI += "file://0050-.gitignore-Add-pulsecore-config.h.patch"
+SRC_URI += "file://0051-pactl-Fix-crash-with-older-servers.patch"
+SRC_URI += "file://0052-volume-Increase-PA_SW_VOLUME_SNPRINT_DB_MAX.patch"
+SRC_URI += "file://0053-sink-input-source-output-Fix-mute-saving.patch"
+SRC_URI += "file://0054-direction-Add-a-couple-of-direction-helper-functions.patch"
+SRC_URI += "file://0055-core-util-Add-pa_join.patch"
+SRC_URI += "file://0056-dynarray-Add-pa_dynarray_get_raw_array.patch"
+SRC_URI += "file://0057-dynarray-Add-PA_DYNARRAY_FOREACH.patch"
+SRC_URI += "file://0058-dynarray-Add-pa_dynarray_remove_last.patch"
+SRC_URI += "file://0059-dynarray-Add-pa_dynarray_remove_all.patch"
+SRC_URI += "file://0060-hashmap-Add-pa_hashmap_remove_and_free.patch"
+SRC_URI += "file://0061-device-port-Add-pa_device_port.active.patch"
+SRC_URI += "file://0062-sink-source-Assign-to-reference_volume-from-only-one.patch"
+SRC_URI += "file://0063-sink-input-source-output-Assign-to-volume-from-only-.patch"
+SRC_URI += "file://0064-sink-source-Return-early-from-set_mute.patch"
+SRC_URI += "file://0065-sink-input-source-output-Add-logging-to-set_mute.patch"
+SRC_URI += "file://0066-sink-source-Allow-calling-set_mute-during-initializa.patch"
+SRC_URI += "file://0067-echo-cancel-Remove-redundant-get_mute-callback.patch"
+SRC_URI += "file://0068-sink-source-Call-set_mute-from-mute_changed.patch"
+SRC_URI += "file://0069-sink-source-Assign-to-s-muted-from-only-one-place.patch"
+SRC_URI += "file://0070-sink-input-source-output-Remove-redundant-get_mute-f.patch"
+SRC_URI += "file://0071-solaris-tunnel-Remove-some-redundant-boolean-convers.patch"
+SRC_URI += "file://0072-sink-source-Add-hooks-for-volume-changes.patch"
+SRC_URI += "file://0073-sink-input-source-output-Add-hooks-for-volume-change.patch"
+SRC_URI += "file://0074-sink-source-Add-hooks-for-mute-changes.patch"
+SRC_URI += "file://0075-sink-input-source-output-Add-hooks-for-mute-changes.patch"
+SRC_URI += "file://0076-sink-Link-monitor-source-before-activating-port.patch"
+SRC_URI += "file://0077-context-extension-Add-the-pa_extension-class.patch"
+SRC_URI += "file://0078-volume-api-Add-libvolume-api.so.patch.gz"
+SRC_URI += "file://0079-Add-module-volume-api-and-the-related-client-API.patch.gz"
+SRC_URI += "file://0080-pactl-Add-support-for-the-new-volume-API.patch"
+SRC_URI += "file://0081-Add-module-audio-groups.patch"
+SRC_URI += "file://0082-Add-module-main-volume-policy.patch"
+SRC_URI += "file://0083-configuration-Add-default-IVI-audio-group-and-main-v.patch"
+SRC_URI += "file://0084-sink-source-Initialize-port-before-fixate-hook-fixes.patch"
+SRC_URI += "file://0085-configuration-pulseaudio-tizen-configuration-in-defa.patch"
+SRC_URI += "file://0086-pactl-Fix-crash-in-pactl-list.patch"
+SRC_URI += "file://0087-configuration-x-example-x-tizen-ivi-in-volume-config.patch"
+SRC_URI += "file://0088-device-creator-stream-creator-Add-a-couple-of-assert.patch"
+SRC_URI += "file://0089-main-volume-policy-Fix-a-memory-leak.patch"
+SRC_URI += "file://0090-audio-groups-fix-issues-found-by-static-analysis.patch"
+SRC_URI += "file://0091-core-util-Add-pa_append_to_home_dir.patch"
+SRC_URI += "file://0092-core-util-Add-pa_get_config_home_dir.patch"
+SRC_URI += "file://0093-core-util-Add-pa_append_to_config_home_dir.patch"
+SRC_URI += "file://0094-core-Create-the-config-home-directory-on-startup.patch"
+SRC_URI += "file://0095-alsa-Handle-unlinking-of-uninitialized-streams-grace.patch"
+SRC_URI += "file://0096-role-cork-Handle-unlinking-of-uninitialized-streams-.patch"
+SRC_URI += "file://0097-role-ducking-Handle-unlinking-of-uninitialized-strea.patch"
+SRC_URI += "file://0098-core-util-Add-pa_boolean_to_string.patch"
+SRC_URI += "file://0099-sink-input-source-output-Assign-to-reference_ratio-f.patch"
+SRC_URI += "file://0100-sink-input-source-output-Add-hooks-for-reference-rat.patch"
+SRC_URI += "file://0101-sink-input-source-output-Use-new_data.volume-only-fo.patch"
+SRC_URI += "file://0102-sink-input-source-output-Add-the-real-object-pointer.patch"
+SRC_URI += "file://0103-audio-groups-main-volume-policy-volume-api-Various-f.patch"
\ No newline at end of file
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0001-volume-ramp-additions-to-the-low-level-infra.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0001-volume-ramp-additions-to-the-low-level-infra.patch
new file mode 100644 (file)
index 0000000..8b2693c
--- /dev/null
@@ -0,0 +1,559 @@
+From: Jaska Uimonen <jaska.uimonen@helsinki.fi>
+Date: Wed, 8 Aug 2012 11:14:37 +0300
+Subject: volume ramp: additions to the low level infra
+
+Change-Id: Ib2be01e0bea0856ebd0256066981fe34d05a4f86
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/map-file        |   4 +
+ src/pulse/def.h     |  13 ++-
+ src/pulse/volume.c  |  74 ++++++++++++-
+ src/pulse/volume.h  |  33 ++++++
+ src/pulsecore/mix.c | 310 ++++++++++++++++++++++++++++++++++++++++++++++++++++
+ src/pulsecore/mix.h |  27 +++++
+ 6 files changed, 459 insertions(+), 2 deletions(-)
+
+diff --git a/src/map-file b/src/map-file
+index fbad1a4..9903942 100644
+--- a/src/map-file
++++ b/src/map-file
+@@ -136,6 +136,10 @@ pa_cvolume_max_mask;
+ pa_cvolume_merge;
+ pa_cvolume_min;
+ pa_cvolume_min_mask;
++pa_cvolume_ramp_equal;
++pa_cvolume_ramp_init;
++pa_cvolume_ramp_set;
++pa_cvolume_ramp_channel_ramp_set;
+ pa_cvolume_remap;
+ pa_cvolume_scale;
+ pa_cvolume_scale_mask;
+diff --git a/src/pulse/def.h b/src/pulse/def.h
+index d6fa912..b7854a3 100644
+--- a/src/pulse/def.h
++++ b/src/pulse/def.h
+@@ -349,11 +349,15 @@ typedef enum pa_stream_flags {
+      * consider absolute when the sink is in flat volume mode,
+      * relative otherwise. \since 0.9.20 */
+-    PA_STREAM_PASSTHROUGH = 0x80000U
++    PA_STREAM_PASSTHROUGH = 0x80000U,
+     /**< Used to tag content that will be rendered by passthrough sinks.
+      * The data will be left as is and not reformatted, resampled.
+      * \since 1.0 */
++    PA_STREAM_START_RAMP_MUTED = 0x100000U
++    /**< Used to tag content that the stream will be started ramp volume
++     * muted so that you can nicely fade it in */
++
+ } pa_stream_flags_t;
+ /** \cond fulldocs */
+@@ -382,6 +386,7 @@ typedef enum pa_stream_flags {
+ #define PA_STREAM_FAIL_ON_SUSPEND PA_STREAM_FAIL_ON_SUSPEND
+ #define PA_STREAM_RELATIVE_VOLUME PA_STREAM_RELATIVE_VOLUME
+ #define PA_STREAM_PASSTHROUGH PA_STREAM_PASSTHROUGH
++#define PA_STREAM_START_RAMP_MUTED PA_STREAM_START_RAMP_MUTED
+ /** \endcond */
+@@ -1049,6 +1054,12 @@ typedef enum pa_port_available {
+ /** \endcond */
+ #endif
++/** \cond fulldocs */
++#define PA_VOLUMER_RAMP_TYPE_LINEAR PA_VOLUMER_RAMP_TYPE_LINEAR
++#define PA_VOLUMER_RAMP_TYPE_LOGARITHMIC PA_VOLUMER_RAMP_TYPE_LOGARITHMIC
++#define PA_VOLUMER_RAMP_TYPE_CUBIC PA_VOLUMER_RAMP_TYPE_CUBIC
++/** \endcond */
++
+ PA_C_DECL_END
+ #endif
+diff --git a/src/pulse/volume.c b/src/pulse/volume.c
+index 6927392..b140cee 100644
+--- a/src/pulse/volume.c
++++ b/src/pulse/volume.c
+@@ -447,7 +447,10 @@ int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) {
+     unsigned c;
+     pa_assert(a);
+-    pa_return_val_if_fail(pa_cvolume_valid(a), 0);
++    if (pa_cvolume_valid(a) == 0)
++        abort();
++
++    /* pa_return_val_if_fail(pa_cvolume_valid(a), 0); */
+     pa_return_val_if_fail(PA_VOLUME_IS_VALID(v), 0);
+     for (c = 0; c < a->channels; c++)
+@@ -988,3 +991,72 @@ pa_cvolume* pa_cvolume_dec(pa_cvolume *v, pa_volume_t dec) {
+     return pa_cvolume_scale(v, m);
+ }
++
++int pa_cvolume_ramp_equal(const pa_cvolume_ramp *a, const pa_cvolume_ramp *b) {
++    int i;
++    pa_assert(a);
++    pa_assert(b);
++
++    if (PA_UNLIKELY(a == b))
++        return 1;
++
++    if (a->channels != b->channels)
++        return 0;
++
++    for (i = 0; i < a->channels; i++) {
++        if (a->ramps[i].type != b->ramps[i].type ||
++            a->ramps[i].length != b->ramps[i].length ||
++            a->ramps[i].target != b->ramps[i].target)
++            return 0;
++    }
++
++    return 1;
++}
++
++pa_cvolume_ramp* pa_cvolume_ramp_init(pa_cvolume_ramp *ramp) {
++    unsigned c;
++
++    pa_assert(ramp);
++
++    ramp->channels = 0;
++
++    for (c = 0; c < PA_CHANNELS_MAX; c++) {
++        ramp->ramps[c].type = PA_VOLUME_RAMP_TYPE_LINEAR;
++        ramp->ramps[c].length = 0;
++        ramp->ramps[c].target = PA_VOLUME_INVALID;
++    }
++
++    return ramp;
++}
++
++pa_cvolume_ramp* pa_cvolume_ramp_set(pa_cvolume_ramp *ramp, unsigned channels, pa_volume_ramp_type_t type, long time, pa_volume_t vol) {
++    int i;
++
++    pa_assert(ramp);
++    pa_assert(channels > 0);
++    pa_assert(time >= 0);
++    pa_assert(channels <= PA_CHANNELS_MAX);
++
++    ramp->channels = (uint8_t) channels;
++
++    for (i = 0; i < ramp->channels; i++) {
++        ramp->ramps[i].type = type;
++        ramp->ramps[i].length = time;
++        ramp->ramps[i].target = PA_CLAMP_VOLUME(vol);
++    }
++
++    return ramp;
++}
++
++pa_cvolume_ramp* pa_cvolume_ramp_channel_ramp_set(pa_cvolume_ramp *ramp, unsigned channel, pa_volume_ramp_type_t type, long time, pa_volume_t vol) {
++
++    pa_assert(ramp);
++    pa_assert(channel <= ramp->channels);
++    pa_assert(time >= 0);
++
++    ramp->ramps[channel].type = type;
++    ramp->ramps[channel].length = time;
++    ramp->ramps[channel].target = PA_CLAMP_VOLUME(vol);
++
++    return ramp;
++}
+diff --git a/src/pulse/volume.h b/src/pulse/volume.h
+index 7806954..3ffb573 100644
+--- a/src/pulse/volume.h
++++ b/src/pulse/volume.h
+@@ -415,6 +415,39 @@ pa_cvolume* pa_cvolume_inc(pa_cvolume *v, pa_volume_t inc);
+  * the channels are kept. \since 0.9.16 */
+ pa_cvolume* pa_cvolume_dec(pa_cvolume *v, pa_volume_t dec);
++/** Volume ramp type
++*/
++typedef enum pa_volume_ramp_type {
++    PA_VOLUME_RAMP_TYPE_LINEAR = 0,        /**< linear */
++    PA_VOLUME_RAMP_TYPE_LOGARITHMIC = 1,   /**< logarithmic */
++    PA_VOLUME_RAMP_TYPE_CUBIC = 2,
++} pa_volume_ramp_type_t;
++
++/** A structure encapsulating a volume ramp */
++typedef struct pa_volume_ramp_t {
++    pa_volume_ramp_type_t type;
++    long length;
++    pa_volume_t target;
++} pa_volume_ramp_t;
++
++/** A structure encapsulating a multichannel volume ramp */
++typedef struct pa_cvolume_ramp {
++    uint8_t channels;
++    pa_volume_ramp_t ramps[PA_CHANNELS_MAX];
++} pa_cvolume_ramp;
++
++/** Return non-zero when *a == *b */
++int pa_cvolume_ramp_equal(const pa_cvolume_ramp *a, const pa_cvolume_ramp *b);
++
++/** Init volume ramp struct */
++pa_cvolume_ramp* pa_cvolume_ramp_init(pa_cvolume_ramp *ramp);
++
++/** Set first n channels of ramp struct to certain value */
++pa_cvolume_ramp* pa_cvolume_ramp_set(pa_cvolume_ramp *ramp, unsigned channel, pa_volume_ramp_type_t type, long time, pa_volume_t vol);
++
++/** Set individual channel in the channel struct */
++pa_cvolume_ramp* pa_cvolume_ramp_channel_ramp_set(pa_cvolume_ramp *ramp, unsigned channel, pa_volume_ramp_type_t type, long time, pa_volume_t vol);
++
+ PA_C_DECL_END
+ #endif
+diff --git a/src/pulsecore/mix.c b/src/pulsecore/mix.c
+index 4b789a6..c1e0c18 100644
+--- a/src/pulsecore/mix.c
++++ b/src/pulsecore/mix.c
+@@ -721,3 +721,313 @@ void pa_volume_memchunk(
+     pa_memblock_release(c->memblock);
+ }
++
++static void calc_linear_integer_volume_no_mapping(int32_t linear[], float volume[], unsigned nchannels) {
++    unsigned channel, padding;
++
++    pa_assert(linear);
++    pa_assert(volume);
++
++    for (channel = 0; channel < nchannels; channel++)
++        linear[channel] = (int32_t) lrint(volume[channel] * 0x10000U);
++
++    for (padding = 0; padding < VOLUME_PADDING; padding++, channel++)
++        linear[channel] = linear[padding];
++}
++
++static void calc_linear_float_volume_no_mapping(float linear[], float volume[], unsigned nchannels) {
++    unsigned channel, padding;
++
++    pa_assert(linear);
++    pa_assert(volume);
++
++    for (channel = 0; channel < nchannels; channel++)
++        linear[channel] = volume[channel];
++
++    for (padding = 0; padding < VOLUME_PADDING; padding++, channel++)
++        linear[channel] = linear[padding];
++}
++
++typedef void (*pa_calc_volume_no_mapping_func_t) (void *volumes, float *volume, int channels);
++
++static const pa_calc_volume_no_mapping_func_t calc_volume_table_no_mapping[] = {
++  [PA_SAMPLE_U8]        = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping,
++  [PA_SAMPLE_ALAW]      = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping,
++  [PA_SAMPLE_ULAW]      = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping,
++  [PA_SAMPLE_S16LE]     = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping,
++  [PA_SAMPLE_S16BE]     = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping,
++  [PA_SAMPLE_FLOAT32LE] = (pa_calc_volume_no_mapping_func_t) calc_linear_float_volume_no_mapping,
++  [PA_SAMPLE_FLOAT32BE] = (pa_calc_volume_no_mapping_func_t) calc_linear_float_volume_no_mapping,
++  [PA_SAMPLE_S32LE]     = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping,
++  [PA_SAMPLE_S32BE]     = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping,
++  [PA_SAMPLE_S24LE]     = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping,
++  [PA_SAMPLE_S24BE]     = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping,
++  [PA_SAMPLE_S24_32LE]  = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping,
++  [PA_SAMPLE_S24_32BE]  = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping
++};
++
++static const unsigned format_sample_size_table[] = {
++  [PA_SAMPLE_U8]        = 1,
++  [PA_SAMPLE_ALAW]      = 1,
++  [PA_SAMPLE_ULAW]      = 1,
++  [PA_SAMPLE_S16LE]     = 2,
++  [PA_SAMPLE_S16BE]     = 2,
++  [PA_SAMPLE_FLOAT32LE] = 4,
++  [PA_SAMPLE_FLOAT32BE] = 4,
++  [PA_SAMPLE_S32LE]     = 4,
++  [PA_SAMPLE_S32BE]     = 4,
++  [PA_SAMPLE_S24LE]     = 3,
++  [PA_SAMPLE_S24BE]     = 3,
++  [PA_SAMPLE_S24_32LE]  = 4,
++  [PA_SAMPLE_S24_32BE]  = 4
++};
++
++static float calc_volume_ramp_linear(pa_volume_ramp_int_t *ramp) {
++    pa_assert(ramp);
++    pa_assert(ramp->length > 0);
++
++    /* basic linear interpolation */
++    return ramp->start + (ramp->length - ramp->left) * (ramp->end - ramp->start) / (float) ramp->length;
++}
++
++static float calc_volume_ramp_logarithmic(pa_volume_ramp_int_t *ramp) {
++    float x_val, s, e;
++    long temp;
++
++    pa_assert(ramp);
++    pa_assert(ramp->length > 0);
++
++    if (ramp->end > ramp->start) {
++        temp = ramp->left;
++        s = ramp->end;
++        e = ramp->start;
++    } else {
++        temp = ramp->length - ramp->left;
++        s = ramp->start;
++        e = ramp->end;
++    }
++
++    x_val = temp == 0 ? 0.0 : powf(temp, 10);
++
++    /* base 10 logarithmic interpolation */
++    return s + x_val * (e - s) / powf(ramp->length, 10);
++}
++
++static float calc_volume_ramp_cubic(pa_volume_ramp_int_t *ramp) {
++    float x_val, s, e;
++    long temp;
++
++    pa_assert(ramp);
++    pa_assert(ramp->length > 0);
++
++    if (ramp->end > ramp->start) {
++        temp = ramp->left;
++        s = ramp->end;
++        e = ramp->start;
++    } else {
++        temp = ramp->length - ramp->left;
++        s = ramp->start;
++        e = ramp->end;
++    }
++
++    x_val = temp == 0 ? 0.0 : cbrtf(temp);
++
++    /* cubic interpolation */
++    return s + x_val * (e - s) / cbrtf(ramp->length);
++}
++
++typedef float (*pa_calc_volume_ramp_func_t) (pa_volume_ramp_int_t *);
++
++static const pa_calc_volume_ramp_func_t calc_volume_ramp_table[] = {
++    [PA_VOLUME_RAMP_TYPE_LINEAR] = (pa_calc_volume_ramp_func_t) calc_volume_ramp_linear,
++    [PA_VOLUME_RAMP_TYPE_LOGARITHMIC] = (pa_calc_volume_ramp_func_t) calc_volume_ramp_logarithmic,
++    [PA_VOLUME_RAMP_TYPE_CUBIC] = (pa_calc_volume_ramp_func_t) calc_volume_ramp_cubic
++};
++
++static void calc_volume_ramps(pa_cvolume_ramp_int *ram, float *vol)
++{
++    int i;
++
++    for (i = 0; i < ram->channels; i++) {
++        if (ram->ramps[i].left <= 0) {
++            if (ram->ramps[i].target == PA_VOLUME_NORM) {
++                vol[i] = 1.0;
++            }
++        } else {
++            vol[i] = ram->ramps[i].curr = calc_volume_ramp_table[ram->ramps[i].type] (&ram->ramps[i]);
++            ram->ramps[i].left--;
++        }
++    }
++}
++
++void pa_volume_ramp_memchunk(
++        pa_memchunk *c,
++        const pa_sample_spec *spec,
++        pa_cvolume_ramp_int *ramp) {
++
++    void *ptr;
++    volume_val linear[PA_CHANNELS_MAX + VOLUME_PADDING];
++    float vol[PA_CHANNELS_MAX + VOLUME_PADDING];
++    pa_do_volume_func_t do_volume;
++    long length_in_frames;
++    int i;
++
++    pa_assert(c);
++    pa_assert(spec);
++    pa_assert(pa_frame_aligned(c->length, spec));
++    pa_assert(ramp);
++
++    length_in_frames = c->length / format_sample_size_table[spec->format] / spec->channels;
++
++    if (pa_memblock_is_silence(c->memblock)) {
++        for (i = 0; i < ramp->channels; i++) {
++            if (ramp->ramps[i].length > 0)
++                ramp->ramps[i].length -= length_in_frames;
++        }
++        return;
++    }
++
++    if (spec->format < 0 || spec->format >= PA_SAMPLE_MAX) {
++      pa_log_warn("Unable to change volume of format");
++      return;
++    }
++
++    do_volume = pa_get_volume_func(spec->format);
++    pa_assert(do_volume);
++
++    ptr = (uint8_t*) pa_memblock_acquire(c->memblock) + c->index;
++
++    for (i = 0; i < length_in_frames; i++) {
++        calc_volume_ramps(ramp, vol);
++        calc_volume_table_no_mapping[spec->format] ((void *)linear, vol, spec->channels);
++
++        /* we only process one frame per iteration */
++        do_volume (ptr, (void *)linear, spec->channels, format_sample_size_table[spec->format] * spec->channels);
++
++        /* pa_log_debug("1: %d  2: %d", linear[0], linear[1]); */
++
++        ptr = (uint8_t*)ptr + format_sample_size_table[spec->format] * spec->channels;
++    }
++
++    pa_memblock_release(c->memblock);
++}
++
++pa_cvolume_ramp_int* pa_cvolume_ramp_convert(const pa_cvolume_ramp *src, pa_cvolume_ramp_int *dst, int sample_rate) {
++
++    int i, j, channels, remaining_channels;
++    float temp;
++
++    if (dst->channels < src->channels) {
++        channels = dst->channels;
++        remaining_channels = 0;
++    }
++    else {
++        channels = src->channels;
++        remaining_channels = dst->channels;
++    }
++
++    for (i = 0; i < channels; i++) {
++        dst->ramps[i].type = src->ramps[i].type;
++        /* ms to samples */
++        dst->ramps[i].length = src->ramps[i].length * sample_rate / 1000;
++        dst->ramps[i].left = dst->ramps[i].length;
++        dst->ramps[i].start = dst->ramps[i].end;
++        dst->ramps[i].target = src->ramps[i].target;
++        /* scale to pulse internal mapping so that when ramp is over there's no glitch in volume */
++        temp = src->ramps[i].target / (float)0x10000U;
++        dst->ramps[i].end = temp * temp * temp;
++    }
++
++    j = i;
++
++    for (i--; j < remaining_channels; j++) {
++        dst->ramps[j].type = dst->ramps[i].type;
++        dst->ramps[j].length = dst->ramps[i].length;
++        dst->ramps[j].left = dst->ramps[i].left;
++        dst->ramps[j].start = dst->ramps[i].start;
++        dst->ramps[j].target = dst->ramps[i].target;
++        dst->ramps[j].end = dst->ramps[i].end;
++    }
++
++    return dst;
++}
++
++bool pa_cvolume_ramp_active(pa_cvolume_ramp_int *ramp) {
++    int i;
++
++    for (i = 0; i < ramp->channels; i++) {
++        if (ramp->ramps[i].left > 0)
++            return true;
++    }
++
++    return false;
++}
++
++bool pa_cvolume_ramp_target_active(pa_cvolume_ramp_int *ramp) {
++    int i;
++
++    for (i = 0; i < ramp->channels; i++) {
++        if (ramp->ramps[i].target != PA_VOLUME_NORM)
++            return true;
++    }
++
++    return false;
++}
++
++pa_cvolume * pa_cvolume_ramp_get_targets(pa_cvolume_ramp_int *ramp, pa_cvolume *volume) {
++    int i = 0;
++
++    volume->channels = ramp->channels;
++
++    for (i = 0; i < ramp->channels; i++)
++        volume->values[i] = ramp->ramps[i].target;
++
++    return volume;
++}
++
++pa_cvolume_ramp_int* pa_cvolume_ramp_start_from(pa_cvolume_ramp_int *src, pa_cvolume_ramp_int *dst) {
++    int i;
++
++    for (i = 0; i < src->channels; i++) {
++        /* if new vols are invalid, copy old ramp i.e. no effect */
++        if (dst->ramps[i].target == PA_VOLUME_INVALID)
++            dst->ramps[i] = src->ramps[i];
++        /* if there's some old ramp still left */
++        else if (src->ramps[i].left > 0)
++            dst->ramps[i].start = src->ramps[i].curr;
++    }
++
++    return dst;
++}
++
++pa_cvolume_ramp_int* pa_cvolume_ramp_int_init(pa_cvolume_ramp_int *src, pa_volume_t vol, int channels) {
++    int i;
++    float temp;
++
++    src->channels = channels;
++
++    for (i = 0; i < channels; i++) {
++        src->ramps[i].type = PA_VOLUME_RAMP_TYPE_LINEAR;
++        src->ramps[i].length = 0;
++        src->ramps[i].left = 0;
++        if (vol == PA_VOLUME_NORM) {
++            src->ramps[i].start = 1.0;
++            src->ramps[i].end = 1.0;
++            src->ramps[i].curr = 1.0;
++        }
++        else if (vol == PA_VOLUME_MUTED) {
++            src->ramps[i].start = 0.0;
++            src->ramps[i].end = 0.0;
++            src->ramps[i].curr = 0.0;
++        }
++        else {
++            temp = vol / (float)0x10000U;
++            src->ramps[i].start = temp * temp * temp;
++            src->ramps[i].end = src->ramps[i].start;
++            src->ramps[i].curr = src->ramps[i].start;
++        }
++        src->ramps[i].target = vol;
++    }
++
++    return src;
++}
+diff --git a/src/pulsecore/mix.h b/src/pulsecore/mix.h
+index a4cde01..b1ec74f 100644
+--- a/src/pulsecore/mix.h
++++ b/src/pulsecore/mix.h
+@@ -61,4 +61,31 @@ void pa_volume_memchunk(
+     const pa_sample_spec *spec,
+     const pa_cvolume *volume);
++typedef struct pa_volume_ramp_int_t {
++    pa_volume_ramp_type_t type;
++    long length;
++    long left;
++    float start;
++    float end;
++    float curr;
++    pa_volume_t target;
++} pa_volume_ramp_int_t;
++
++typedef struct pa_cvolume_ramp_int {
++    uint8_t channels;
++    pa_volume_ramp_int_t ramps[PA_CHANNELS_MAX];
++} pa_cvolume_ramp_int;
++
++pa_cvolume_ramp_int* pa_cvolume_ramp_convert(const pa_cvolume_ramp *src, pa_cvolume_ramp_int *dst, int sample_rate);
++bool pa_cvolume_ramp_active(pa_cvolume_ramp_int *ramp);
++bool pa_cvolume_ramp_target_active(pa_cvolume_ramp_int *ramp);
++pa_cvolume_ramp_int* pa_cvolume_ramp_start_from(pa_cvolume_ramp_int *src, pa_cvolume_ramp_int *dst);
++pa_cvolume_ramp_int* pa_cvolume_ramp_int_init(pa_cvolume_ramp_int *src, pa_volume_t vol, int channels);
++pa_cvolume * pa_cvolume_ramp_get_targets(pa_cvolume_ramp_int *ramp, pa_cvolume *volume);
++
++void pa_volume_ramp_memchunk(
++        pa_memchunk *c,
++        const pa_sample_spec *spec,
++        pa_cvolume_ramp_int *ramp);
++
+ #endif
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0002-volume-ramp-add-volume-ramping-to-sink.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0002-volume-ramp-add-volume-ramping-to-sink.patch
new file mode 100644 (file)
index 0000000..4ea7c20
--- /dev/null
@@ -0,0 +1,243 @@
+From: Jaska Uimonen <jaska.uimonen@helsinki.fi>
+Date: Wed, 8 Aug 2012 11:14:39 +0300
+Subject: volume ramp: add volume ramping to sink
+
+Change-Id: I72bd64b4e4ad161ae0526a7e40d4bf9e843ae98c
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/sink.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++---
+ src/pulsecore/sink.h |  9 +++++
+ 2 files changed, 96 insertions(+), 5 deletions(-)
+
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index f4647b8..61656ab 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -333,6 +333,8 @@ pa_sink* pa_sink_new(
+             &s->sample_spec,
+             0);
++    pa_cvolume_ramp_int_init(&s->ramp, PA_VOLUME_NORM, data->sample_spec.channels);
++
+     s->thread_info.rtpoll = NULL;
+     s->thread_info.inputs = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL,
+                                                 (pa_free_cb_t) pa_sink_input_unref);
+@@ -356,6 +358,8 @@ pa_sink* pa_sink_new(
+     s->thread_info.volume_change_extra_delay = core->deferred_volume_extra_delay_usec;
+     s->thread_info.latency_offset = s->latency_offset;
++    s->thread_info.ramp = s->ramp;
++
+     /* FIXME: This should probably be moved to pa_sink_put() */
+     pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0);
+@@ -1189,6 +1193,7 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) {
+     } else if (n == 1) {
+         pa_cvolume volume;
++        pa_cvolume target;
+         *result = info[0].chunk;
+         pa_memblock_ref(result->memblock);
+@@ -1205,12 +1210,25 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) {
+                                     result,
+                                     &s->sample_spec,
+                                     result->length);
+-        } else if (!pa_cvolume_is_norm(&volume)) {
++        } else if (!pa_cvolume_is_norm(&volume) || pa_cvolume_ramp_target_active(&s->thread_info.ramp) || pa_cvolume_ramp_active(&s->thread_info.ramp)) {
+             pa_memchunk_make_writable(result, 0);
+-            pa_volume_memchunk(result, &s->sample_spec, &volume);
++            if (pa_cvolume_ramp_active(&s->thread_info.ramp)) {
++                if (!pa_cvolume_is_norm(&volume))
++                    pa_volume_memchunk(result, &s->sample_spec, &volume);
++                pa_volume_ramp_memchunk(result, &s->sample_spec, &(s->thread_info.ramp));
++            }
++            else {
++                if (pa_cvolume_ramp_target_active(&s->thread_info.ramp)) {
++                    pa_cvolume_ramp_get_targets(&s->thread_info.ramp, &target);
++                    pa_sw_cvolume_multiply(&volume, &volume, &target);
++                }
++                pa_volume_memchunk(result, &s->sample_spec, &volume);
++            }
+         }
+     } else {
+         void *ptr;
++        pa_cvolume target_vol;
++
+         result->memblock = pa_memblock_new(s->core->mempool, length);
+         ptr = pa_memblock_acquire(result->memblock);
+@@ -1219,6 +1237,16 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) {
+                                 &s->sample_spec,
+                                 &s->thread_info.soft_volume,
+                                 s->thread_info.soft_muted);
++
++        if (pa_cvolume_ramp_target_active(&s->thread_info.ramp) || pa_cvolume_ramp_active(&s->thread_info.ramp)) {
++            if (pa_cvolume_ramp_active(&s->thread_info.ramp))
++                pa_volume_ramp_memchunk(result, &s->sample_spec, &(s->thread_info.ramp));
++                else {
++                    pa_cvolume_ramp_get_targets(&s->thread_info.ramp, &target_vol);
++                    pa_volume_memchunk(result, &s->sample_spec, &target_vol);
++                }
++        }
++
+         pa_memblock_release(result->memblock);
+         result->index = 0;
+@@ -1279,6 +1307,7 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) {
+             pa_silence_memchunk(target, &s->sample_spec);
+         else {
+             pa_memchunk vchunk;
++            pa_cvolume target_vol;
+             vchunk = info[0].chunk;
+             pa_memblock_ref(vchunk.memblock);
+@@ -1286,9 +1315,20 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) {
+             if (vchunk.length > length)
+                 vchunk.length = length;
+-            if (!pa_cvolume_is_norm(&volume)) {
++            if (!pa_cvolume_is_norm(&volume) || pa_cvolume_ramp_target_active(&s->thread_info.ramp) || pa_cvolume_ramp_active(&s->thread_info.ramp)) {
+                 pa_memchunk_make_writable(&vchunk, 0);
+-                pa_volume_memchunk(&vchunk, &s->sample_spec, &volume);
++                if (pa_cvolume_ramp_active(&s->thread_info.ramp)) {
++                    if (!pa_cvolume_is_norm(&volume))
++                        pa_volume_memchunk(&vchunk, &s->sample_spec, &volume);
++                    pa_volume_ramp_memchunk(&vchunk, &s->sample_spec, &(s->thread_info.ramp));
++                }
++                else {
++                    if (pa_cvolume_ramp_target_active(&s->thread_info.ramp)) {
++                        pa_cvolume_ramp_get_targets(&s->thread_info.ramp, &target_vol);
++                        pa_sw_cvolume_multiply(&volume, &volume, &target_vol);
++                    }
++                    pa_volume_memchunk(&vchunk, &s->sample_spec, &volume);
++                }
+             }
+             pa_memchunk_memcpy(target, &vchunk);
+@@ -1297,6 +1337,7 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) {
+     } else {
+         void *ptr;
++        pa_cvolume target_vol;
+         ptr = pa_memblock_acquire(target->memblock);
+@@ -1306,6 +1347,15 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) {
+                                 &s->thread_info.soft_volume,
+                                 s->thread_info.soft_muted);
++        if (pa_cvolume_ramp_target_active(&s->thread_info.ramp) || pa_cvolume_ramp_active(&s->thread_info.ramp)) {
++            if (pa_cvolume_ramp_active(&s->thread_info.ramp))
++                pa_volume_ramp_memchunk(target, &s->sample_spec, &(s->thread_info.ramp));
++            else {
++                pa_cvolume_ramp_get_targets(&s->thread_info.ramp, &target_vol);
++                pa_volume_memchunk(target, &s->sample_spec, &target_vol);
++            }
++        }
++
+         pa_memblock_release(target->memblock);
+     }
+@@ -2085,6 +2135,32 @@ void pa_sink_set_volume(
+         pa_assert_se(pa_asyncmsgq_send(root_sink->asyncmsgq, PA_MSGOBJECT(root_sink), PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL) == 0);
+ }
++/* Called from main thread */
++void pa_sink_set_volume_ramp(
++        pa_sink *s,
++        const pa_cvolume_ramp *ramp,
++        bool send_msg,
++        bool save) {
++
++    pa_sink_assert_ref(s);
++    pa_assert_ctl_context();
++    pa_assert(PA_SINK_IS_LINKED(s->state));
++    pa_assert(ramp);
++
++    /* make sure we don't change the volume when a PASSTHROUGH input is connected ...
++     * ... *except* if we're being invoked to reset the volume to ensure 0 dB gain */
++    if (pa_sink_is_passthrough(s)) {
++        pa_log_warn("Cannot do volume ramp, Sink is connected to PASSTHROUGH input");
++        return;
++    }
++
++    pa_cvolume_ramp_convert(ramp, &s->ramp, s->sample_spec.rate);
++
++    /* This tells the sink that volume ramp changed */
++    if (send_msg)
++        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME_RAMP, NULL, 0, NULL) == 0);
++}
++
+ /* Called from the io thread if sync volume is used, otherwise from the main thread.
+  * Only to be called by sink implementor */
+ void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) {
+@@ -2737,13 +2813,19 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
+                 s->thread_info.soft_volume = s->soft_volume;
+                 pa_sink_request_rewind(s, (size_t) -1);
+             }
+-
+             /* Fall through ... */
+         case PA_SINK_MESSAGE_SYNC_VOLUMES:
+             sync_input_volumes_within_thread(s);
+             return 0;
++        case PA_SINK_MESSAGE_SET_VOLUME_RAMP:
++            /* if we have ongoing ramp where we take current start values */
++            pa_cvolume_ramp_start_from(&s->thread_info.ramp, &s->ramp);
++            s->thread_info.ramp = s->ramp;
++            pa_sink_request_rewind(s, (size_t) -1);
++            return 0;
++
+         case PA_SINK_MESSAGE_GET_VOLUME:
+             if ((s->flags & PA_SINK_DEFERRED_VOLUME) && s->get_volume) {
+diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
+index c7cb5f8..576f34b 100644
+--- a/src/pulsecore/sink.h
++++ b/src/pulsecore/sink.h
+@@ -37,6 +37,7 @@ typedef struct pa_sink_volume_change pa_sink_volume_change;
+ #include <pulsecore/core.h>
+ #include <pulsecore/idxset.h>
+ #include <pulsecore/memchunk.h>
++#include <pulsecore/mix.h>
+ #include <pulsecore/source.h>
+ #include <pulsecore/module.h>
+ #include <pulsecore/asyncmsgq.h>
+@@ -105,6 +106,9 @@ struct pa_sink {
+     pa_cvolume saved_volume;
+     bool saved_save_volume:1;
++    /* for volume ramps */
++    pa_cvolume_ramp_int ramp;
++
+     pa_asyncmsgq *asyncmsgq;
+     pa_memchunk silence;
+@@ -288,6 +292,8 @@ struct pa_sink {
+         uint32_t volume_change_safety_margin;
+         /* Usec delay added to all volume change events, may be negative. */
+         int32_t volume_change_extra_delay;
++
++        pa_cvolume_ramp_int ramp;
+     } thread_info;
+     void *userdata;
+@@ -322,6 +328,7 @@ typedef enum pa_sink_message {
+     PA_SINK_MESSAGE_SET_PORT,
+     PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE,
+     PA_SINK_MESSAGE_SET_LATENCY_OFFSET,
++    PA_SINK_MESSAGE_SET_VOLUME_RAMP,
+     PA_SINK_MESSAGE_MAX
+ } pa_sink_message_t;
+@@ -443,6 +450,8 @@ bool pa_sink_get_mute(pa_sink *sink, bool force_refresh);
+ bool pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p);
++void pa_sink_set_volume_ramp(pa_sink *s, const pa_cvolume_ramp *ramp, bool send_msg, bool save);
++
+ int pa_sink_set_port(pa_sink *s, const char *name, bool save);
+ void pa_sink_set_mixer_dirty(pa_sink *s, bool is_dirty);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0003-volume-ramp-adding-volume-ramping-to-sink-input.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0003-volume-ramp-adding-volume-ramping-to-sink-input.patch
new file mode 100644 (file)
index 0000000..9557dc8
--- /dev/null
@@ -0,0 +1,181 @@
+From: Jaska Uimonen <jaska.uimonen@helsinki.fi>
+Date: Wed, 8 Aug 2012 11:14:38 +0300
+Subject: volume ramp: adding volume ramping to sink-input
+
+Change-Id: I9376f531f3585c9ba5fb6d1b384589a8d123b292
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/sink-input.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++
+ src/pulsecore/sink-input.h | 12 +++++++++-
+ 2 files changed, 70 insertions(+), 1 deletion(-)
+
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index f85b2c7..05ab25d 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -527,6 +527,11 @@ int pa_sink_input_new(
+     reset_callbacks(i);
+     i->userdata = NULL;
++    if (data->flags & PA_SINK_INPUT_START_RAMP_MUTED)
++        pa_cvolume_ramp_int_init(&i->ramp, PA_VOLUME_MUTED, data->sink->sample_spec.channels);
++    else
++        pa_cvolume_ramp_int_init(&i->ramp, PA_VOLUME_NORM, data->sink->sample_spec.channels);
++
+     i->thread_info.state = i->state;
+     i->thread_info.attached = false;
+     pa_atomic_store(&i->thread_info.drained, 1);
+@@ -543,6 +548,8 @@ int pa_sink_input_new(
+     i->thread_info.playing_for = 0;
+     i->thread_info.direct_outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
++    i->thread_info.ramp = i->ramp;
++
+     pa_assert_se(pa_idxset_put(core->sink_inputs, i, &i->index) == 0);
+     pa_assert_se(pa_idxset_put(i->sink->inputs, pa_sink_input_ref(i), NULL) == 0);
+@@ -924,6 +931,8 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink bytes */, pa
+         while (tchunk.length > 0) {
+             pa_memchunk wchunk;
+             bool nvfs = need_volume_factor_sink;
++            pa_cvolume target;
++            bool tmp;
+             wchunk = tchunk;
+             pa_memblock_ref(wchunk.memblock);
+@@ -960,6 +969,16 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink bytes */, pa
+                     pa_volume_memchunk(&wchunk, &i->sink->sample_spec, &i->volume_factor_sink);
+                 }
++                /* check for possible volume ramp */
++                if (pa_cvolume_ramp_active(&i->thread_info.ramp)) {
++                    pa_memchunk_make_writable(&wchunk, 0);
++                    pa_volume_ramp_memchunk(&wchunk, &i->sink->sample_spec, &(i->thread_info.ramp));
++                } else if ((tmp = pa_cvolume_ramp_target_active(&(i->thread_info.ramp)))) {
++                    pa_memchunk_make_writable(&wchunk, 0);
++                    pa_cvolume_ramp_get_targets(&i->thread_info.ramp, &target);
++                    pa_volume_memchunk(&wchunk, &i->sink->sample_spec, &target);
++                }
++
+                 pa_memblockq_push_align(i->thread_info.render_memblockq, &wchunk);
+             } else {
+                 pa_memchunk rchunk;
+@@ -976,6 +995,16 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink bytes */, pa
+                         pa_volume_memchunk(&rchunk, &i->sink->sample_spec, &i->volume_factor_sink);
+                     }
++                    /* check for possible volume ramp */
++                    if (pa_cvolume_ramp_active(&(i->thread_info.ramp))) {
++                        pa_memchunk_make_writable(&rchunk, 0);
++                        pa_volume_ramp_memchunk(&rchunk, &i->sink->sample_spec, &(i->thread_info.ramp));
++                    } else if (pa_cvolume_ramp_target_active(&(i->thread_info.ramp))) {
++                        pa_memchunk_make_writable(&rchunk, 0);
++                        pa_cvolume_ramp_get_targets(&i->thread_info.ramp, &target);
++                        pa_volume_memchunk(&rchunk, &i->sink->sample_spec, &target);
++                    }
++
+                     pa_memblockq_push_align(i->thread_info.render_memblockq, &rchunk);
+                     pa_memblock_unref(rchunk.memblock);
+                 }
+@@ -1351,6 +1380,29 @@ int pa_sink_input_remove_volume_factor(pa_sink_input *i, const char *key) {
+     return 0;
+ }
++/* Called from main thread */
++void pa_sink_input_set_volume_ramp(
++        pa_sink_input *i,
++        const pa_cvolume_ramp *ramp,
++        bool send_msg,
++        bool save) {
++
++    pa_sink_input_assert_ref(i);
++    pa_assert_ctl_context();
++    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
++    pa_assert(ramp);
++
++    pa_cvolume_ramp_convert(ramp, &i->ramp, i->sample_spec.rate);
++
++    pa_log_debug("setting volume ramp with target vol:%d and length:%ld",
++               i->ramp.ramps[0].target,
++               i->ramp.ramps[0].length);
++
++    /* This tells the sink that volume ramp changed */
++    if (send_msg)
++        pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_VOLUME_RAMP, NULL, 0, NULL) == 0);
++}
++
+ /* Called from main context */
+ static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v) {
+     pa_sink_input_assert_ref(i);
+@@ -1968,6 +2020,13 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t
+             }
+             return 0;
++        case PA_SINK_INPUT_MESSAGE_SET_VOLUME_RAMP:
++            /* we have ongoing ramp where we take current start values */
++            pa_cvolume_ramp_start_from(&i->thread_info.ramp, &i->ramp);
++            i->thread_info.ramp = i->ramp;
++            pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
++            return 0;
++
+         case PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE:
+             if (i->thread_info.muted != i->muted) {
+                 i->thread_info.muted = i->muted;
+diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
+index da33717..3f74054 100644
+--- a/src/pulsecore/sink-input.h
++++ b/src/pulsecore/sink-input.h
+@@ -35,6 +35,7 @@ typedef struct pa_sink_input pa_sink_input;
+ #include <pulsecore/client.h>
+ #include <pulsecore/sink.h>
+ #include <pulsecore/core.h>
++#include <pulsecore/mix.h>
+ typedef enum pa_sink_input_state {
+     PA_SINK_INPUT_INIT,         /*< The stream is not active yet, because pa_sink_input_put() has not been called yet */
+@@ -61,7 +62,8 @@ typedef enum pa_sink_input_flags {
+     PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND = 256,
+     PA_SINK_INPUT_NO_CREATE_ON_SUSPEND = 512,
+     PA_SINK_INPUT_KILL_ON_SUSPEND = 1024,
+-    PA_SINK_INPUT_PASSTHROUGH = 2048
++    PA_SINK_INPUT_PASSTHROUGH = 2048,
++    PA_SINK_INPUT_START_RAMP_MUTED = 4096,
+ } pa_sink_input_flags_t;
+ struct pa_sink_input {
+@@ -124,6 +126,9 @@ struct pa_sink_input {
+      * this.*/
+     bool save_sink:1, save_volume:1, save_muted:1;
++    /* for volume ramps */
++    pa_cvolume_ramp_int ramp;
++
+     pa_resample_method_t requested_resample_method, actual_resample_method;
+     /* Returns the chunk of audio data and drops it from the
+@@ -252,6 +257,8 @@ struct pa_sink_input {
+         pa_usec_t requested_sink_latency;
+         pa_hashmap *direct_outputs;
++
++        pa_cvolume_ramp_int ramp;
+     } thread_info;
+     void *userdata;
+@@ -268,6 +275,7 @@ enum {
+     PA_SINK_INPUT_MESSAGE_SET_STATE,
+     PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY,
+     PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY,
++    PA_SINK_INPUT_MESSAGE_SET_VOLUME_RAMP,
+     PA_SINK_INPUT_MESSAGE_MAX
+ };
+@@ -377,6 +385,8 @@ pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, bool
+ void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save);
+ bool pa_sink_input_get_mute(pa_sink_input *i);
++void pa_sink_input_set_volume_ramp(pa_sink_input *i, const pa_cvolume_ramp *ramp, bool send_msg, bool save);
++
+ void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p);
+ pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0004-build-sys-install-files-for-a-module-development.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0004-build-sys-install-files-for-a-module-development.patch
new file mode 100644 (file)
index 0000000..8b43385
--- /dev/null
@@ -0,0 +1,72 @@
+From: Jaska Uimonen <jaska.uimonen@helsinki.fi>
+Date: Tue, 5 Jun 2012 11:36:13 +0300
+Subject: build-sys: install files for a module development.
+
+Change-Id: Ib68b292e1f6bc82bb5c148ef53acf51cc571406e
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ Makefile.am                   | 11 ++++++++++-
+ configure.ac                  |  1 +
+ pulseaudio-module-devel.pc.in | 12 ++++++++++++
+ 3 files changed, 23 insertions(+), 1 deletion(-)
+ create mode 100644 pulseaudio-module-devel.pc.in
+
+diff --git a/Makefile.am b/Makefile.am
+index b0b2553..a6a0b40 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -50,7 +50,13 @@ dist_vapi_DATA = \
+               vala/libpulse-mainloop-glib.deps vala/libpulse-mainloop-glib.vapi
+ pkgconfigdir = $(libdir)/pkgconfig
+-pkgconfig_DATA = libpulse.pc libpulse-simple.pc
++pkgconfig_DATA = libpulse.pc libpulse-simple.pc pulseaudio-module-devel.pc
++
++moduledev_DATA = pulsecore-config.h src/pulsecore/*.h
++moduledevdir   = $(includedir)/pulsemodule/pulsecore
++
++moduledevinternal_DATA = src/pulse/internal.h src/pulse/client-conf.h src/pulse/fork-detect.h
++moduledevinternaldir   = $(includedir)/pulsemodule/pulse
+ if HAVE_GLIB20
+ pkgconfig_DATA += \
+@@ -89,6 +95,9 @@ dist-hook:
+ check-daemon:
+       $(MAKE) -C src check-daemon
++pulsecore-config.h: config.h
++      cp $< $@
++
+ .PHONY: homepage distcleancheck doxygen
+ # see git-version-gen
+diff --git a/configure.ac b/configure.ac
+index 4854711..4e9f97e 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -1384,6 +1384,7 @@ man/default.pa.5.xml
+ man/pulse-cli-syntax.5.xml
+ man/start-pulseaudio-kde.1.xml
+ man/start-pulseaudio-x11.1.xml
++pulseaudio-module-devel.pc
+ ])
+ AC_CONFIG_FILES([src/esdcompat:src/daemon/esdcompat.in], [chmod +x src/esdcompat])
+diff --git a/pulseaudio-module-devel.pc.in b/pulseaudio-module-devel.pc.in
+new file mode 100644
+index 0000000..85aadbc
+--- /dev/null
++++ b/pulseaudio-module-devel.pc.in
+@@ -0,0 +1,12 @@
++prefix=@prefix@
++exec_prefix=@exec_prefix@
++libdir=@libdir@
++includedir=@includedir@
++modlibexecdir=@modlibexecdir@
++
++Name: pulseaudio-module-devel
++Description: PulseAudio Module Development Interface
++Version: @PACKAGE_VERSION@
++Libs: -L${libdir} -L${libdir}/pulseaudio -L${modlibexecdir} -lpulsecommon-@PA_MAJORMINOR@ -lpulsecore-@PA_MAJORMINOR@ -lprotocol-native
++Libs.private:
++Cflags: -I${includedir}/pulsemodule -D_REENTRANT
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0005-jack-detection-fix-for-wired-headset.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0005-jack-detection-fix-for-wired-headset.patch
new file mode 100644 (file)
index 0000000..002e4ec
--- /dev/null
@@ -0,0 +1,23 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Sun, 10 Jun 2012 15:13:11 +0300
+Subject: jack detection fix for wired headset
+
+Change-Id: I53d465bf56adc2e3e5551b43d59ff99b63bc76cc
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/alsa/mixer/paths/analog-output-headphones.conf | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
+index 89d794f..4771bc6 100644
+--- a/src/modules/alsa/mixer/paths/analog-output-headphones.conf
++++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
+@@ -95,7 +95,7 @@ volume = off
+ ; On some machines Front is actually a part of the Headphone path
+ [Element Front]
+-switch = mute
++switch = off
+ volume = zero
+ [Element Rear]
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0006-make-pa_thread_mq_done-safe-for-subsequent-calls.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0006-make-pa_thread_mq_done-safe-for-subsequent-calls.patch
new file mode 100644 (file)
index 0000000..bfbf3d9
--- /dev/null
@@ -0,0 +1,29 @@
+From: Janos Kovacs <jankovac503@gmail.com>
+Date: Thu, 16 Aug 2012 03:47:48 +0300
+Subject: make pa_thread_mq_done() safe for subsequent calls
+
+Change-Id: I71ab9efa72c38a2200a56b7a4d30116767bc104f
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/thread-mq.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/src/pulsecore/thread-mq.c b/src/pulsecore/thread-mq.c
+index d34b22b..f3c5b6c 100644
+--- a/src/pulsecore/thread-mq.c
++++ b/src/pulsecore/thread-mq.c
+@@ -144,6 +144,14 @@ void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop, pa_rtpoll *rt
+ void pa_thread_mq_done(pa_thread_mq *q) {
+     pa_assert(q);
++    if (!q->main_mainloop && !q->inq && !q->outq &&
++        !q->read_main_event && !q->write_main_event)
++        return;
++
++    pa_assert(q->main_mainloop);
++    pa_assert(q->inq && q->outq);
++    pa_assert(q->read_main_event && q->write_main_event);
++
+     /* Since we are called from main context we can be sure that the
+      * inq is empty. However, the outq might still contain messages
+      * for the main loop, which we need to dispatch (e.g. release
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0007-node-manager-adding-external-node-manager-API.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0007-node-manager-adding-external-node-manager-API.patch
new file mode 100644 (file)
index 0000000..5deba2b
--- /dev/null
@@ -0,0 +1,568 @@
+From: Ismo Puustinen <ismo.puustinen@intel.com>
+Date: Wed, 28 May 2014 11:11:56 +0300
+Subject: node manager: adding external node manager API
+
+Change-Id: I4f220f05b9de513a7003b3cea761c3540fa74685
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/Makefile.am              |   4 +
+ src/map-file                 |  11 ++
+ src/pulse/context.c          |   5 +
+ src/pulse/ext-node-manager.c | 348 +++++++++++++++++++++++++++++++++++++++++++
+ src/pulse/ext-node-manager.h |  93 ++++++++++++
+ src/pulse/internal.h         |   6 +
+ 6 files changed, 467 insertions(+)
+ create mode 100644 src/pulse/ext-node-manager.c
+ create mode 100644 src/pulse/ext-node-manager.h
+
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 857fda3..e1808e6 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -753,6 +753,8 @@ pulseinclude_HEADERS = \
+               pulse/ext-device-manager.h \
+               pulse/ext-device-restore.h \
+               pulse/ext-stream-restore.h \
++              pulse/ext-echo-cancel.h \
++              pulse/ext-node-manager.h \
+               pulse/format.h \
+               pulse/gccmacro.h \
+               pulse/introspect.h \
+@@ -798,6 +800,8 @@ libpulse_la_SOURCES = \
+               pulse/ext-device-manager.c pulse/ext-device-manager.h \
+               pulse/ext-device-restore.c pulse/ext-device-restore.h \
+               pulse/ext-stream-restore.c pulse/ext-stream-restore.h \
++              pulse/ext-echo-cancel.c pulse/ext-echo-cancel.h \
++              pulse/ext-node-manager.c pulse/ext-node-manager.h \
+               pulse/format.c pulse/format.h \
+               pulse/gccmacro.h \
+               pulse/internal.h \
+diff --git a/src/map-file b/src/map-file
+index 9903942..7dbcd00 100644
+--- a/src/map-file
++++ b/src/map-file
+@@ -171,6 +171,17 @@ pa_ext_stream_restore_set_subscribe_cb;
+ pa_ext_stream_restore_subscribe;
+ pa_ext_stream_restore_test;
+ pa_ext_stream_restore_write;
++pa_ext_policy_test;
++pa_ext_policy_set_mono;
++pa_ext_policy_set_balance;
++pa_ext_echo_cancel_set_volume;
++pa_ext_echo_cancel_set_device;
++pa_ext_node_manager_test;
++pa_ext_node_manager_read_nodes;
++pa_ext_node_manager_connect_nodes;
++pa_ext_node_manager_disconnect_nodes;
++pa_ext_node_manager_subscribe;
++pa_ext_node_manager_set_subscribe_cb;
+ pa_format_info_copy;
+ pa_format_info_free;
+ pa_format_info_from_string;
+diff --git a/src/pulse/context.c b/src/pulse/context.c
+index b78df27..b8688f2 100644
+--- a/src/pulse/context.c
++++ b/src/pulse/context.c
+@@ -122,6 +122,9 @@ static void reset_callbacks(pa_context *c) {
+     c->ext_stream_restore.callback = NULL;
+     c->ext_stream_restore.userdata = NULL;
++
++    c->ext_node_manager.callback = NULL;
++    c->ext_node_manager.userdata = NULL;
+ }
+ pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, pa_proplist *p) {
+@@ -1361,6 +1364,8 @@ void pa_command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_t
+         pa_ext_device_restore_command(c, tag, t);
+     else if (pa_streq(name, "module-stream-restore"))
+         pa_ext_stream_restore_command(c, tag, t);
++    else if (pa_streq(name, "module-node-manager"))
++        pa_ext_node_manager_command(c, tag, t);
+     else
+         pa_log(_("Received message for unknown extension '%s'"), name);
+diff --git a/src/pulse/ext-node-manager.c b/src/pulse/ext-node-manager.c
+new file mode 100644
+index 0000000..5cb3feb
+--- /dev/null
++++ b/src/pulse/ext-node-manager.c
+@@ -0,0 +1,348 @@
++/***
++  This file is part of PulseAudio.
++
++  Copyright 2012 Jaska Uimonen
++  Copyright 2012 Janos Kovacs
++
++  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 <pulse/context.h>
++#include <pulse/xmalloc.h>
++#include <pulse/fork-detect.h>
++#include <pulse/operation.h>
++
++#include <pulsecore/macro.h>
++#include <pulsecore/pstream-util.h>
++
++#include "internal.h"
++#include "ext-node-manager.h"
++
++enum {
++    SUBCOMMAND_TEST,
++    SUBCOMMAND_READ,
++    SUBCOMMAND_CONNECT,
++    SUBCOMMAND_DISCONNECT,
++    SUBCOMMAND_SUBSCRIBE,
++    SUBCOMMAND_EVENT
++};
++
++static void ext_node_manager_test_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
++    pa_operation *o = userdata;
++    uint32_t version = PA_INVALID_INDEX;
++
++    pa_assert(pd);
++    pa_assert(o);
++    pa_assert(PA_REFCNT_VALUE(o) >= 1);
++
++    if (!o->context)
++        goto finish;
++
++    if (command != PA_COMMAND_REPLY) {
++        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
++            goto finish;
++
++    } else if (pa_tagstruct_getu32(t, &version) < 0 ||
++               !pa_tagstruct_eof(t)) {
++
++        pa_context_fail(o->context, PA_ERR_PROTOCOL);
++        goto finish;
++    }
++
++    if (o->callback) {
++        pa_ext_node_manager_test_cb_t cb = (pa_ext_node_manager_test_cb_t) o->callback;
++        cb(o->context, version, o->userdata);
++    }
++
++finish:
++    pa_operation_done(o);
++    pa_operation_unref(o);
++}
++
++pa_operation *pa_ext_node_manager_test(
++        pa_context *c,
++        pa_ext_node_manager_test_cb_t cb,
++        void *userdata) {
++
++    uint32_t tag;
++    pa_operation *o;
++    pa_tagstruct *t;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED);
++
++    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++    t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag);
++    pa_tagstruct_putu32(t, PA_INVALID_INDEX);
++    pa_tagstruct_puts(t, "module-murphy-ivi");
++    pa_tagstruct_putu32(t, SUBCOMMAND_TEST);
++    pa_pstream_send_tagstruct(c->pstream, t);
++    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_node_manager_test_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++    return o;
++}
++
++static void ext_node_manager_read_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
++    pa_operation *o = userdata;
++    pa_ext_node_manager_info i;
++
++    pa_assert(pd);
++    pa_assert(o);
++    pa_assert(PA_REFCNT_VALUE(o) >= 1);
++
++    if (!o->context)
++        goto finish;
++
++    if (command != PA_COMMAND_REPLY) {
++        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
++            goto finish;
++    } else {
++
++        while (!pa_tagstruct_eof(t)) {
++
++            memset(&i, 0, sizeof(i));
++
++          i.props = pa_proplist_new();
++
++            if (pa_tagstruct_gets(t, &i.name) < 0 ||
++                pa_tagstruct_get_proplist(t, i.props) < 0) {
++
++                pa_context_fail(o->context, PA_ERR_PROTOCOL);
++                goto finish;
++            }
++
++            if (o->callback) {
++                pa_ext_node_manager_read_cb_t cb = (pa_ext_node_manager_read_cb_t) o->callback;
++                cb(o->context, &i, 0, o->userdata);
++            }
++
++          pa_proplist_free(i.props);
++        }
++
++      /* let's send end marker */
++      if (o->callback) {
++                pa_ext_node_manager_read_cb_t cb = (pa_ext_node_manager_read_cb_t) o->callback;
++                cb(o->context, &i, 1, o->userdata);
++            }
++    }
++
++finish:
++    pa_operation_done(o);
++    pa_operation_unref(o);
++}
++
++pa_operation *pa_ext_node_manager_read_nodes(
++        pa_context *c,
++        pa_ext_node_manager_read_cb_t cb,
++        void *userdata) {
++
++    uint32_t tag;
++    pa_operation *o;
++    pa_tagstruct *t;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED);
++
++    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++    t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag);
++    pa_tagstruct_putu32(t, PA_INVALID_INDEX);
++    pa_tagstruct_puts(t, "module-murphy-ivi");
++    pa_tagstruct_putu32(t, SUBCOMMAND_READ);
++    pa_pstream_send_tagstruct(c->pstream, t);
++    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_node_manager_read_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++    return o;
++}
++
++static void ext_node_manager_connect_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
++    pa_operation *o = userdata;
++    uint32_t connection = PA_INVALID_INDEX;
++
++    pa_assert(pd);
++    pa_assert(o);
++    pa_assert(PA_REFCNT_VALUE(o) >= 1);
++
++    if (!o->context)
++        goto finish;
++
++    if (command != PA_COMMAND_REPLY) {
++        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
++            goto finish;
++
++    } else if (pa_tagstruct_getu32(t, &connection) < 0 ||
++               !pa_tagstruct_eof(t)) {
++
++        pa_context_fail(o->context, PA_ERR_PROTOCOL);
++        goto finish;
++    }
++
++    if (o->callback) {
++        pa_ext_node_manager_connect_cb_t cb = (pa_ext_node_manager_connect_cb_t) o->callback;
++        cb(o->context, connection, o->userdata);
++    }
++
++finish:
++    pa_operation_done(o);
++    pa_operation_unref(o);
++}
++
++pa_operation *pa_ext_node_manager_connect_nodes(
++        pa_context *c,
++        uint32_t source_node_id,
++        uint32_t sink_node_id,
++        pa_ext_node_manager_connect_cb_t cb,
++        void *userdata) {
++
++    uint32_t tag;
++    pa_operation *o = NULL;
++    pa_tagstruct *t = NULL;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED);
++
++    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++    t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag);
++    pa_tagstruct_putu32(t, PA_INVALID_INDEX);
++    pa_tagstruct_puts(t, "module-murphy-ivi");
++    pa_tagstruct_putu32(t, SUBCOMMAND_CONNECT);
++
++    pa_tagstruct_putu32(t, source_node_id);
++    pa_tagstruct_putu32(t, sink_node_id);
++
++    pa_pstream_send_tagstruct(c->pstream, t);
++    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_node_manager_connect_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++    return o;
++}
++
++pa_operation *pa_ext_node_manager_disconnect_nodes(
++        pa_context *c,
++      uint32_t conn_id,
++        pa_context_success_cb_t cb,
++        void *userdata) {
++
++    uint32_t tag;
++    pa_operation *o = NULL;
++    pa_tagstruct *t = NULL;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED);
++
++    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++    t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag);
++    pa_tagstruct_putu32(t, PA_INVALID_INDEX);
++    pa_tagstruct_puts(t, "module-murphy-ivi");
++    pa_tagstruct_putu32(t, SUBCOMMAND_DISCONNECT);
++
++    pa_tagstruct_putu32(t, conn_id);
++
++    pa_pstream_send_tagstruct(c->pstream, t);
++    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++    return o;
++}
++
++pa_operation *pa_ext_node_manager_subscribe(
++        pa_context *c,
++        int enable,
++        pa_context_success_cb_t cb,
++        void *userdata) {
++
++    uint32_t tag;
++    pa_operation *o;
++    pa_tagstruct *t;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED);
++
++    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++    t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag);
++    pa_tagstruct_putu32(t, PA_INVALID_INDEX);
++    pa_tagstruct_puts(t, "module-murphy-ivi");
++    pa_tagstruct_putu32(t, SUBCOMMAND_SUBSCRIBE);
++    pa_tagstruct_put_boolean(t, enable);
++    pa_pstream_send_tagstruct(c->pstream, t);
++    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++    return o;
++}
++
++void pa_ext_node_manager_set_subscribe_cb(
++        pa_context *c,
++        pa_ext_node_manager_subscribe_cb_t cb,
++        void *userdata) {
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++
++    if (pa_detect_fork())
++        return;
++
++    c->ext_node_manager.callback = cb;
++    c->ext_node_manager.userdata = userdata;
++}
++
++void pa_ext_node_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t) {
++    uint32_t subcommand;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++    pa_assert(t);
++
++    if (pa_tagstruct_getu32(t, &subcommand) < 0 ||
++        !pa_tagstruct_eof(t)) {
++
++        pa_context_fail(c, PA_ERR_PROTOCOL);
++        return;
++    }
++
++    if (subcommand != SUBCOMMAND_EVENT) {
++        pa_context_fail(c, PA_ERR_PROTOCOL);
++        return;
++    }
++
++    if (c->ext_node_manager.callback)
++        c->ext_node_manager.callback(c, c->ext_node_manager.userdata);
++}
+diff --git a/src/pulse/ext-node-manager.h b/src/pulse/ext-node-manager.h
+new file mode 100644
+index 0000000..57b9f01
+--- /dev/null
++++ b/src/pulse/ext-node-manager.h
+@@ -0,0 +1,93 @@
++#ifndef foopulseextnodemanagerhfoo
++#define foopulseextnodemanagerhfoo
++
++/***
++  This file is part of PulseAudio.
++
++  Copyright 2012 Jaska Uimonen
++  Copyright 2012 Janos Kovacs
++
++  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/version.h>
++
++PA_C_DECL_BEGIN
++
++typedef struct pa_ext_node_manager_info {
++    const char *name;
++    pa_proplist *props;
++} pa_ext_node_manager_info;
++
++typedef void (*pa_ext_node_manager_test_cb_t)(
++        pa_context *c,
++        uint32_t version,
++        void *userdata);
++
++pa_operation *pa_ext_node_manager_test(
++        pa_context *c,
++        pa_ext_node_manager_test_cb_t cb,
++        void *userdata);
++
++typedef void (*pa_ext_node_manager_read_cb_t)(
++        pa_context *c,
++        const pa_ext_node_manager_info *info,
++        int eol,
++        void *userdata);
++
++pa_operation *pa_ext_node_manager_read_nodes(
++        pa_context *c,
++        pa_ext_node_manager_read_cb_t cb,
++        void *userdata);
++
++typedef void (*pa_ext_node_manager_connect_cb_t)(
++        pa_context *c,
++        uint32_t connection,
++        void *userdata);
++
++pa_operation *pa_ext_node_manager_connect_nodes(
++        pa_context *c,
++        uint32_t src,
++        uint32_t dst,
++        pa_ext_node_manager_connect_cb_t cb,
++        void *userdata);
++
++pa_operation *pa_ext_node_manager_disconnect_nodes(
++        pa_context *c,
++        uint32_t conn,
++        pa_context_success_cb_t cb,
++        void *userdata);
++
++pa_operation *pa_ext_node_manager_subscribe(
++        pa_context *c,
++        int enable,
++        pa_context_success_cb_t cb,
++        void *userdata);
++
++typedef void (*pa_ext_node_manager_subscribe_cb_t)(
++        pa_context *c,
++        void *userdata);
++
++void pa_ext_node_manager_set_subscribe_cb(
++        pa_context *c,
++        pa_ext_node_manager_subscribe_cb_t cb,
++        void *userdata);
++
++PA_C_DECL_END
++
++#endif
+diff --git a/src/pulse/internal.h b/src/pulse/internal.h
+index c5084d5..61095d0 100644
+--- a/src/pulse/internal.h
++++ b/src/pulse/internal.h
+@@ -31,6 +31,7 @@
+ #include <pulse/ext-device-manager.h>
+ #include <pulse/ext-device-restore.h>
+ #include <pulse/ext-stream-restore.h>
++#include <pulse/ext-node-manager.h>
+ #include <pulsecore/socket-client.h>
+ #include <pulsecore/pstream.h>
+@@ -115,6 +116,10 @@ struct pa_context {
+         pa_ext_stream_restore_subscribe_cb_t callback;
+         void *userdata;
+     } ext_stream_restore;
++    struct {
++        pa_ext_node_manager_subscribe_cb_t callback;
++        void *userdata;
++    } ext_node_manager;
+ };
+ #define PA_MAX_WRITE_INDEX_CORRECTIONS 32
+@@ -303,6 +308,7 @@ pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *ta
+ void pa_ext_device_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t);
+ void pa_ext_device_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t);
+ void pa_ext_stream_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t);
++void pa_ext_node_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t);
+ bool pa_mainloop_is_our_api(pa_mainloop_api*m);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0008-node-manager-adding-node-support-for-pactl.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0008-node-manager-adding-node-support-for-pactl.patch
new file mode 100644 (file)
index 0000000..64cc9c3
--- /dev/null
@@ -0,0 +1,152 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Wed, 5 Dec 2012 09:53:12 +0200
+Subject: node-manager: adding node support for pactl
+
+Change-Id: Id6badeba2181ef4afa9842307e4c6c60f72c472f
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/utils/pactl.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
+ 1 file changed, 72 insertions(+), 3 deletions(-)
+
+diff --git a/src/utils/pactl.c b/src/utils/pactl.c
+index 40e6689..1d8faa4 100644
+--- a/src/utils/pactl.c
++++ b/src/utils/pactl.c
+@@ -38,6 +38,7 @@
+ #include <pulse/pulseaudio.h>
+ #include <pulse/ext-device-restore.h>
++#include <pulse/ext-node-manager.h>
+ #include <pulsecore/i18n.h>
+ #include <pulsecore/macro.h>
+@@ -97,6 +98,10 @@ static int actions = 1;
+ static bool nl = false;
++static uint32_t src_node_id;
++static uint32_t dst_node_id;
++static uint32_t conn_id;
++
+ static enum {
+     NONE,
+     EXIT,
+@@ -127,7 +132,9 @@ static enum {
+     SET_SOURCE_OUTPUT_MUTE,
+     SET_SINK_FORMATS,
+     SET_PORT_LATENCY_OFFSET,
+-    SUBSCRIBE
++    SUBSCRIBE,
++    NODE_CONNECT,
++    NODE_DISCONNECT
+ } action = NONE;
+ static void quit(int ret) {
+@@ -1007,6 +1014,30 @@ static void source_output_toggle_mute_callback(pa_context *c, const pa_source_ou
+     pa_operation_unref(pa_context_set_source_output_mute(c, o->index, !o->mute, simple_callback, NULL));
+ }
++static void node_list_callback(pa_context *c,
++                             const pa_ext_node_manager_info *info,
++                             int eol,
++                             void *userdata) {
++
++    if (!eol) {
++      const char *node_id = pa_proplist_gets(info->props, "index");
++      if (node_id != NULL) {
++          printf("Node #%s (%s)\n", node_id, info->name);
++          printf("%s\n", pa_proplist_to_string(info->props));
++      }
++    } else
++      complete_action();
++}
++
++static void node_connect_callback(pa_context *c,
++                          uint32_t conne_id,
++                          void *userdata) {
++
++    printf("New connection id: %d\n", conne_id);
++
++    complete_action();
++}
++
+ /* PA_MAX_FORMATS is defined in internal.h so we just define a sane value here */
+ #define MAX_FORMATS 256
+@@ -1212,6 +1243,8 @@ static void context_state_callback(pa_context *c, void *userdata) {
+                             pa_operation_unref(pa_context_get_sample_info_list(c, get_sample_info_callback, NULL));
+                         else if (pa_streq(list_type, "cards"))
+                             pa_operation_unref(pa_context_get_card_info_list(c, get_card_info_callback, NULL));
++                      else if (pa_streq(list_type, "nodes"))
++                          pa_operation_unref(pa_ext_node_manager_read_nodes(c, node_list_callback, NULL));
+                         else
+                             pa_assert_not_reached();
+                     } else {
+@@ -1373,6 +1406,18 @@ static void context_state_callback(pa_context *c, void *userdata) {
+                                               NULL,
+                                               NULL));
+                     break;
++              case NODE_CONNECT:
++                  pa_operation_unref(pa_ext_node_manager_connect_nodes(c,
++                                                                       src_node_id,
++                                                                       dst_node_id,
++                                                                       node_connect_callback,
++                                                                       NULL));
++                  break;
++              case NODE_DISCONNECT:
++                  pa_operation_unref(pa_ext_node_manager_disconnect_nodes(c, conn_id,
++                                                                      simple_callback,
++                                                                      NULL));
++                  break;
+                 default:
+                     pa_assert_not_reached();
+@@ -1494,6 +1539,9 @@ static void help(const char *argv0) {
+     printf("%s %s %s\n",    argv0, _("[options]"), "subscribe");
+     printf(_("\nThe special names @DEFAULT_SINK@, @DEFAULT_SOURCE@ and @DEFAULT_MONITOR@\n"
+              "can be used to specify the default sink, source and monitor.\n"));
++    printf("%s %s %s\n",    argv0, _("[options]"), "node-list ");
++    printf("%s %s %s %s %s\n", argv0, _("[options]"), "node-connect ", _("#N"), _("#N"));
++    printf("%s %s %s %s\n", argv0, _("[options]"), "node-disconnect ", _("#N"));
+     printf(_("\n"
+              "  -h, --help                            Show this help\n"
+@@ -1590,7 +1638,7 @@ int main(int argc, char *argv[]) {
+                 if (pa_streq(argv[i], "modules") || pa_streq(argv[i], "clients") ||
+                     pa_streq(argv[i], "sinks")   || pa_streq(argv[i], "sink-inputs") ||
+                     pa_streq(argv[i], "sources") || pa_streq(argv[i], "source-outputs") ||
+-                    pa_streq(argv[i], "samples") || pa_streq(argv[i], "cards")) {
++                    pa_streq(argv[i], "samples") || pa_streq(argv[i], "cards") || pa_streq(argv[i], "nodes")) {
+                     list_type = pa_xstrdup(argv[i]);
+                 } else if (pa_streq(argv[i], "short")) {
+                     short_list_format = true;
+@@ -1960,7 +2008,28 @@ int main(int argc, char *argv[]) {
+                 goto quit;
+             }
+-        } else if (pa_streq(argv[optind], "help")) {
++        } else if (pa_streq(argv[optind], "node-connect")) {
++          action = NODE_CONNECT;
++
++          if (argc != optind+3) {
++              pa_log(_("You have to specify a source and destination node indexes"));
++              goto quit;
++          }
++
++          src_node_id = (uint32_t) atoi(argv[optind+1]);
++          dst_node_id = (uint32_t) atoi(argv[optind+2]);
++
++      } else if (pa_streq(argv[optind], "node-disconnect")) {
++          action = NODE_DISCONNECT;
++
++          if (argc != optind+2) {
++              pa_log(_("You have to specify a connection id"));
++              goto quit;
++          }
++
++          conn_id = (uint32_t) atoi(argv[optind+1]);
++
++      } else if (pa_streq(argv[optind], "help")) {
+             help(bn);
+             ret = 0;
+             goto quit;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0009-add-internal-corking-state-for-sink-input.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0009-add-internal-corking-state-for-sink-input.patch
new file mode 100644 (file)
index 0000000..3ff665d
--- /dev/null
@@ -0,0 +1,87 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Thu, 7 Mar 2013 13:41:44 +0200
+Subject: add internal corking state for sink-input
+
+Change-Id: Iaa7ef16c798e06c0fdf11097690c6cf49215773d
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/sink-input.c | 30 +++++++++++++++++++++++++++++-
+ src/pulsecore/sink-input.h |  4 ++++
+ 2 files changed, 33 insertions(+), 1 deletion(-)
+
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index 05ab25d..dff6324 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -788,6 +788,9 @@ void pa_sink_input_put(pa_sink_input *i) {
+     update_n_corked(i, state);
+     i->state = state;
++    i->corked = FALSE;
++    i->corked_internal = FALSE;
++
+     /* We might need to update the sink's volume if we are in flat volume mode. */
+     if (pa_sink_flat_volume_enabled(i->sink))
+         pa_sink_set_volume(i->sink, NULL, false, i->save_volume);
+@@ -1506,13 +1509,38 @@ void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_p
+     }
+ }
++static void pa_sink_input_cork_really(pa_sink_input *i, bool b) {
++    pa_sink_input_assert_ref(i);
++    pa_assert_ctl_context();
++    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
++
++    if (i->corked_internal == false && i->corked == false)
++      b = false;
++    else
++      b = true;
++
++    sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING);
++}
++
+ /* Called from main context */
+ void pa_sink_input_cork(pa_sink_input *i, bool b) {
+     pa_sink_input_assert_ref(i);
+     pa_assert_ctl_context();
+     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+-    sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING);
++    i->corked = b;
++
++    pa_sink_input_cork_really(i, b);
++}
++
++void pa_sink_input_cork_internal(pa_sink_input *i, bool b) {
++    pa_sink_input_assert_ref(i);
++    pa_assert_ctl_context();
++    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
++
++    i->corked_internal = b;
++
++    pa_sink_input_cork_really(i, b);
+ }
+ /* Called from main context */
+diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
+index 3f74054..deea348 100644
+--- a/src/pulsecore/sink-input.h
++++ b/src/pulsecore/sink-input.h
+@@ -129,6 +129,9 @@ struct pa_sink_input {
+     /* for volume ramps */
+     pa_cvolume_ramp_int ramp;
++    bool corked;
++    bool corked_internal;
++
+     pa_resample_method_t requested_resample_method, actual_resample_method;
+     /* Returns the chunk of audio data and drops it from the
+@@ -360,6 +363,7 @@ implementing the "zero latency" write-through functionality. */
+ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes, bool rewrite, bool flush, bool dont_rewind_render);
+ void pa_sink_input_cork(pa_sink_input *i, bool b);
++void pa_sink_input_cork_internal(pa_sink_input *i, bool b);
+ int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate);
+ int pa_sink_input_update_rate(pa_sink_input *i);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0010-bluetooth-Add-basic-support-for-HEADSET-profiles.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0010-bluetooth-Add-basic-support-for-HEADSET-profiles.patch
new file mode 100644 (file)
index 0000000..3249f6f
--- /dev/null
@@ -0,0 +1,634 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Wed, 10 Jul 2013 09:45:01 -0300
+Subject: bluetooth: Add basic support for HEADSET profiles
+
+This commit adds basic support for devices implementing HSP Headset
+Unit, HSP Audio Gateway, HFP Handsfree Unit, HFP Audio Gateway to the
+BlueZ 5 bluetooth audio devices driver module (module-bluez5-device).
+
+Change-Id: I6be5bcf310a327012f382d408a70e7fdc65caab1
+---
+ src/modules/bluetooth/bluez5-util.c          |   4 +
+ src/modules/bluetooth/bluez5-util.h          |   6 +
+ src/modules/bluetooth/module-bluez5-device.c | 443 ++++++++++++++++++++-------
+ 3 files changed, 345 insertions(+), 108 deletions(-)
+
+diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
+index 7eecc18..ae219ea 100644
+--- a/src/modules/bluetooth/bluez5-util.c
++++ b/src/modules/bluetooth/bluez5-util.c
+@@ -1093,6 +1093,10 @@ const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
+             return "a2dp_sink";
+         case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+             return "a2dp_source";
++        case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
++            return "hsp";
++        case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
++            return "hfgw";
+         case PA_BLUETOOTH_PROFILE_OFF:
+             return "off";
+     }
+diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
+index bbc5b71..841c3c6 100644
+--- a/src/modules/bluetooth/bluez5-util.h
++++ b/src/modules/bluetooth/bluez5-util.h
+@@ -26,6 +26,10 @@
+ #define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
+ #define PA_BLUETOOTH_UUID_A2DP_SINK   "0000110b-0000-1000-8000-00805f9b34fb"
++#define PA_BLUETOOTH_UUID_HSP_HS      "00001108-0000-1000-8000-00805f9b34fb"
++#define PA_BLUETOOTH_UUID_HSP_AG      "00001112-0000-1000-8000-00805f9b34fb"
++#define PA_BLUETOOTH_UUID_HFP_HF      "0000111e-0000-1000-8000-00805f9b34fb"
++#define PA_BLUETOOTH_UUID_HFP_AG      "0000111f-0000-1000-8000-00805f9b34fb"
+ typedef struct pa_bluetooth_transport pa_bluetooth_transport;
+ typedef struct pa_bluetooth_device pa_bluetooth_device;
+@@ -41,6 +45,8 @@ typedef enum pa_bluetooth_hook {
+ typedef enum profile {
+     PA_BLUETOOTH_PROFILE_A2DP_SINK,
+     PA_BLUETOOTH_PROFILE_A2DP_SOURCE,
++    PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT,
++    PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY,
+     PA_BLUETOOTH_PROFILE_OFF
+ } pa_bluetooth_profile_t;
+ #define PA_BLUETOOTH_PROFILE_COUNT PA_BLUETOOTH_PROFILE_OFF
+diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
+index 287e763..b511b92 100644
+--- a/src/modules/bluetooth/module-bluez5-device.c
++++ b/src/modules/bluetooth/module-bluez5-device.c
+@@ -33,6 +33,7 @@
+ #include <pulse/timeval.h>
+ #include <pulsecore/core-error.h>
++#include <pulsecore/core-rtclock.h>
+ #include <pulsecore/core-util.h>
+ #include <pulsecore/i18n.h>
+ #include <pulsecore/module.h>
+@@ -59,7 +60,9 @@ PA_MODULE_USAGE("path=<device object path>");
+ #define MAX_PLAYBACK_CATCH_UP_USEC (100 * PA_USEC_PER_MSEC)
+ #define FIXED_LATENCY_PLAYBACK_A2DP (25 * PA_USEC_PER_MSEC)
++#define FIXED_LATENCY_PLAYBACK_SCO (125 * PA_USEC_PER_MSEC)
+ #define FIXED_LATENCY_RECORD_A2DP   (25 * PA_USEC_PER_MSEC)
++#define FIXED_LATENCY_RECORD_SCO    (25 * PA_USEC_PER_MSEC)
+ #define BITPOOL_DEC_LIMIT 32
+ #define BITPOOL_DEC_STEP 5
+@@ -235,6 +238,167 @@ static void connect_ports(struct userdata *u, void *new_data, pa_direction_t dir
+ }
+ /* Run from IO thread */
++static int sco_process_render(struct userdata *u) {
++    int ret = 0;
++
++    pa_assert(u);
++    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY);
++    pa_assert(u->sink);
++
++    /* First, render some data */
++    if (!u->write_memchunk.memblock)
++        pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
++
++    pa_assert(u->write_memchunk.length == u->write_block_size);
++
++    for (;;) {
++        ssize_t l;
++        const void *p;
++
++        /* Now write that data to the socket. The socket is of type
++         * SEQPACKET, and we generated the data of the MTU size, so this
++         * should just work. */
++
++        p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
++        l = pa_write(u->stream_fd, p, u->write_memchunk.length, &u->stream_write_type);
++        pa_memblock_release(u->write_memchunk.memblock);
++
++        pa_assert(l != 0);
++
++        if (l < 0) {
++
++            if (errno == EINTR)
++                /* Retry right away if we got interrupted */
++                continue;
++
++            else if (errno == EAGAIN)
++                /* Hmm, apparently the socket was not writable, give up for now */
++                break;
++
++            pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(errno));
++            ret = -1;
++            break;
++        }
++
++        pa_assert((size_t) l <= u->write_memchunk.length);
++
++        if ((size_t) l != u->write_memchunk.length) {
++            pa_log_error("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
++                        (unsigned long long) l,
++                        (unsigned long long) u->write_memchunk.length);
++            ret = -1;
++            break;
++        }
++
++        u->write_index += (uint64_t) u->write_memchunk.length;
++        pa_memblock_unref(u->write_memchunk.memblock);
++        pa_memchunk_reset(&u->write_memchunk);
++
++        ret = 1;
++        break;
++    }
++
++    return ret;
++}
++
++/* Run from IO thread */
++static int sco_process_push(struct userdata *u) {
++    int ret = 0;
++    pa_memchunk memchunk;
++
++    pa_assert(u);
++    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY);
++    pa_assert(u->source);
++    pa_assert(u->read_smoother);
++
++    memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
++    memchunk.index = memchunk.length = 0;
++
++    for (;;) {
++        ssize_t l;
++        void *p;
++        struct msghdr m;
++        struct cmsghdr *cm;
++        uint8_t aux[1024];
++        struct iovec iov;
++        bool found_tstamp = false;
++        pa_usec_t tstamp;
++
++        memset(&m, 0, sizeof(m));
++        memset(&aux, 0, sizeof(aux));
++        memset(&iov, 0, sizeof(iov));
++
++        m.msg_iov = &iov;
++        m.msg_iovlen = 1;
++        m.msg_control = aux;
++        m.msg_controllen = sizeof(aux);
++
++        p = pa_memblock_acquire(memchunk.memblock);
++        iov.iov_base = p;
++        iov.iov_len = pa_memblock_get_length(memchunk.memblock);
++        l = recvmsg(u->stream_fd, &m, 0);
++        pa_memblock_release(memchunk.memblock);
++
++        if (l <= 0) {
++
++            if (l < 0 && errno == EINTR)
++                /* Retry right away if we got interrupted */
++                continue;
++
++            else if (l < 0 && errno == EAGAIN)
++                /* Hmm, apparently the socket was not readable, give up for now. */
++                break;
++
++            pa_log_error("Failed to read data from SCO socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
++            ret = -1;
++            break;
++        }
++
++        pa_assert((size_t) l <= pa_memblock_get_length(memchunk.memblock));
++
++        /* In some rare occasions, we might receive packets of a very strange
++         * size. This could potentially be possible if the SCO packet was
++         * received partially over-the-air, or more probably due to hardware
++         * issues in our Bluetooth adapter. In these cases, in order to avoid
++         * an assertion failure due to unaligned data, just discard the whole
++         * packet */
++        if (!pa_frame_aligned(l, &u->sample_spec)) {
++            pa_log_warn("SCO packet received of unaligned size: %zu", l);
++            break;
++        }
++
++        memchunk.length = (size_t) l;
++        u->read_index += (uint64_t) l;
++
++        for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm))
++            if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
++                struct timeval *tv = (struct timeval*) CMSG_DATA(cm);
++                pa_rtclock_from_wallclock(tv);
++                tstamp = pa_timeval_load(tv);
++                found_tstamp = true;
++                break;
++            }
++
++        if (!found_tstamp) {
++            pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!");
++            tstamp = pa_rtclock_now();
++        }
++
++        pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
++        pa_smoother_resume(u->read_smoother, tstamp, true);
++
++        pa_source_post(u->source, &memchunk);
++
++        ret = l;
++        break;
++    }
++
++    pa_memblock_unref(memchunk.memblock);
++
++    return ret;
++}
++
++/* Run from IO thread */
+ static void a2dp_prepare_buffer(struct userdata *u) {
+     size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu);
+@@ -608,24 +772,31 @@ static void transport_release(struct userdata *u) {
+ /* Run from I/O thread */
+ static void transport_config_mtu(struct userdata *u) {
+-    u->read_block_size =
+-        (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+-        / u->sbc_info.frame_length * u->sbc_info.codesize;
++    if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
++        u->read_block_size = u->read_link_mtu;
++        u->write_block_size = u->write_link_mtu;
++    } else {
++        u->read_block_size =
++            (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
++            / u->sbc_info.frame_length * u->sbc_info.codesize;
+-    u->write_block_size =
+-        (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+-        / u->sbc_info.frame_length * u->sbc_info.codesize;
++        u->write_block_size =
++            (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
++            / u->sbc_info.frame_length * u->sbc_info.codesize;
++    }
+     if (u->sink) {
+         pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
+         pa_sink_set_fixed_latency_within_thread(u->sink,
+-                                                FIXED_LATENCY_PLAYBACK_A2DP +
++                                                (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ?
++                                                 FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO) +
+                                                 pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+     }
+     if (u->source)
+         pa_source_set_fixed_latency_within_thread(u->source,
+-                                                  FIXED_LATENCY_RECORD_A2DP +
++                                                  (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE ?
++                                                   FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_SCO) +
+                                                   pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
+ }
+@@ -752,15 +923,19 @@ static int add_source(struct userdata *u) {
+     data.namereg_fail = false;
+     pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
+     pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
++    if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
++        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
+     connect_ports(u, &data, PA_DIRECTION_INPUT);
+     if (!u->transport_acquired)
+         switch (u->profile) {
+             case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
++            case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
+                 data.suspend_cause = PA_SUSPEND_USER;
+                 break;
+             case PA_BLUETOOTH_PROFILE_A2DP_SINK:
++            case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
+             case PA_BLUETOOTH_PROFILE_OFF:
+                 pa_assert_not_reached();
+                 break;
+@@ -867,14 +1042,20 @@ static int add_sink(struct userdata *u) {
+     data.namereg_fail = false;
+     pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
+     pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
++    if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
++        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
+     connect_ports(u, &data, PA_DIRECTION_OUTPUT);
+     if (!u->transport_acquired)
+         switch (u->profile) {
++            case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
++                data.suspend_cause = PA_SUSPEND_USER;
++                break;
+             case PA_BLUETOOTH_PROFILE_A2DP_SINK:
+                 /* Profile switch should have failed */
+             case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
++            case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
+             case PA_BLUETOOTH_PROFILE_OFF:
+                 pa_assert_not_reached();
+                 break;
+@@ -895,111 +1076,117 @@ static int add_sink(struct userdata *u) {
+ /* Run from main thread */
+ static void transport_config(struct userdata *u) {
+-    sbc_info_t *sbc_info = &u->sbc_info;
+-    a2dp_sbc_t *config;
++    if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
++        u->sample_spec.format = PA_SAMPLE_S16LE;
++        u->sample_spec.channels = 1;
++        u->sample_spec.rate = 8000;
++    } else {
++        sbc_info_t *sbc_info = &u->sbc_info;
++        a2dp_sbc_t *config;
+-    pa_assert(u->transport);
++        pa_assert(u->transport);
+-    u->sample_spec.format = PA_SAMPLE_S16LE;
+-    config = (a2dp_sbc_t *) u->transport->config;
++        u->sample_spec.format = PA_SAMPLE_S16LE;
++        config = (a2dp_sbc_t *) u->transport->config;
+-    if (sbc_info->sbc_initialized)
+-        sbc_reinit(&sbc_info->sbc, 0);
+-    else
+-        sbc_init(&sbc_info->sbc, 0);
+-    sbc_info->sbc_initialized = true;
++        if (sbc_info->sbc_initialized)
++            sbc_reinit(&sbc_info->sbc, 0);
++        else
++            sbc_init(&sbc_info->sbc, 0);
++        sbc_info->sbc_initialized = true;
+-    switch (config->frequency) {
+-        case SBC_SAMPLING_FREQ_16000:
+-            sbc_info->sbc.frequency = SBC_FREQ_16000;
+-            u->sample_spec.rate = 16000U;
+-            break;
+-        case SBC_SAMPLING_FREQ_32000:
+-            sbc_info->sbc.frequency = SBC_FREQ_32000;
+-            u->sample_spec.rate = 32000U;
+-            break;
+-        case SBC_SAMPLING_FREQ_44100:
+-            sbc_info->sbc.frequency = SBC_FREQ_44100;
+-            u->sample_spec.rate = 44100U;
+-            break;
+-        case SBC_SAMPLING_FREQ_48000:
+-            sbc_info->sbc.frequency = SBC_FREQ_48000;
+-            u->sample_spec.rate = 48000U;
+-            break;
+-        default:
+-            pa_assert_not_reached();
+-    }
++        switch (config->frequency) {
++            case SBC_SAMPLING_FREQ_16000:
++                sbc_info->sbc.frequency = SBC_FREQ_16000;
++                u->sample_spec.rate = 16000U;
++                break;
++            case SBC_SAMPLING_FREQ_32000:
++                sbc_info->sbc.frequency = SBC_FREQ_32000;
++                u->sample_spec.rate = 32000U;
++                break;
++            case SBC_SAMPLING_FREQ_44100:
++                sbc_info->sbc.frequency = SBC_FREQ_44100;
++                u->sample_spec.rate = 44100U;
++                break;
++            case SBC_SAMPLING_FREQ_48000:
++                sbc_info->sbc.frequency = SBC_FREQ_48000;
++                u->sample_spec.rate = 48000U;
++                break;
++            default:
++                pa_assert_not_reached();
++        }
+-    switch (config->channel_mode) {
+-        case SBC_CHANNEL_MODE_MONO:
+-            sbc_info->sbc.mode = SBC_MODE_MONO;
+-            u->sample_spec.channels = 1;
+-            break;
+-        case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+-            sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
+-            u->sample_spec.channels = 2;
+-            break;
+-        case SBC_CHANNEL_MODE_STEREO:
+-            sbc_info->sbc.mode = SBC_MODE_STEREO;
+-            u->sample_spec.channels = 2;
+-            break;
+-        case SBC_CHANNEL_MODE_JOINT_STEREO:
+-            sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
+-            u->sample_spec.channels = 2;
+-            break;
+-        default:
+-            pa_assert_not_reached();
+-    }
++        switch (config->channel_mode) {
++            case SBC_CHANNEL_MODE_MONO:
++                sbc_info->sbc.mode = SBC_MODE_MONO;
++                u->sample_spec.channels = 1;
++                break;
++            case SBC_CHANNEL_MODE_DUAL_CHANNEL:
++                sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
++                u->sample_spec.channels = 2;
++                break;
++            case SBC_CHANNEL_MODE_STEREO:
++                sbc_info->sbc.mode = SBC_MODE_STEREO;
++                u->sample_spec.channels = 2;
++                break;
++            case SBC_CHANNEL_MODE_JOINT_STEREO:
++                sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
++                u->sample_spec.channels = 2;
++                break;
++            default:
++                pa_assert_not_reached();
++        }
+-    switch (config->allocation_method) {
+-        case SBC_ALLOCATION_SNR:
+-            sbc_info->sbc.allocation = SBC_AM_SNR;
+-            break;
+-        case SBC_ALLOCATION_LOUDNESS:
+-            sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
+-            break;
+-        default:
+-            pa_assert_not_reached();
+-    }
++        switch (config->allocation_method) {
++            case SBC_ALLOCATION_SNR:
++                sbc_info->sbc.allocation = SBC_AM_SNR;
++                break;
++            case SBC_ALLOCATION_LOUDNESS:
++                sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
++                break;
++            default:
++                pa_assert_not_reached();
++        }
+-    switch (config->subbands) {
+-        case SBC_SUBBANDS_4:
+-            sbc_info->sbc.subbands = SBC_SB_4;
+-            break;
+-        case SBC_SUBBANDS_8:
+-            sbc_info->sbc.subbands = SBC_SB_8;
+-            break;
+-        default:
+-            pa_assert_not_reached();
+-    }
++        switch (config->subbands) {
++            case SBC_SUBBANDS_4:
++                sbc_info->sbc.subbands = SBC_SB_4;
++                break;
++            case SBC_SUBBANDS_8:
++                sbc_info->sbc.subbands = SBC_SB_8;
++                break;
++            default:
++                pa_assert_not_reached();
++        }
+-    switch (config->block_length) {
+-        case SBC_BLOCK_LENGTH_4:
+-            sbc_info->sbc.blocks = SBC_BLK_4;
+-            break;
+-        case SBC_BLOCK_LENGTH_8:
+-            sbc_info->sbc.blocks = SBC_BLK_8;
+-            break;
+-        case SBC_BLOCK_LENGTH_12:
+-            sbc_info->sbc.blocks = SBC_BLK_12;
+-            break;
+-        case SBC_BLOCK_LENGTH_16:
+-            sbc_info->sbc.blocks = SBC_BLK_16;
+-            break;
+-        default:
+-            pa_assert_not_reached();
+-    }
++        switch (config->block_length) {
++            case SBC_BLOCK_LENGTH_4:
++                sbc_info->sbc.blocks = SBC_BLK_4;
++                break;
++            case SBC_BLOCK_LENGTH_8:
++                sbc_info->sbc.blocks = SBC_BLK_8;
++                break;
++            case SBC_BLOCK_LENGTH_12:
++                sbc_info->sbc.blocks = SBC_BLK_12;
++                break;
++            case SBC_BLOCK_LENGTH_16:
++                sbc_info->sbc.blocks = SBC_BLK_16;
++                break;
++            default:
++                pa_assert_not_reached();
++        }
+-    sbc_info->min_bitpool = config->min_bitpool;
+-    sbc_info->max_bitpool = config->max_bitpool;
++        sbc_info->min_bitpool = config->min_bitpool;
++        sbc_info->max_bitpool = config->max_bitpool;
+-    /* Set minimum bitpool for source to get the maximum possible block_size */
+-    sbc_info->sbc.bitpool = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ? sbc_info->max_bitpool : sbc_info->min_bitpool;
+-    sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
+-    sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
++        /* Set minimum bitpool for source to get the maximum possible block_size */
++        sbc_info->sbc.bitpool = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ? sbc_info->max_bitpool : sbc_info->min_bitpool;
++        sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
++        sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+-    pa_log_info("SBC parameters: allocation=%u, subbands=%u, blocks=%u, bitpool=%u",
+-                sbc_info->sbc.allocation, sbc_info->sbc.subbands, sbc_info->sbc.blocks, sbc_info->sbc.bitpool);
++        pa_log_info("SBC parameters: allocation=%u, subbands=%u, blocks=%u, bitpool=%u",
++                    sbc_info->sbc.allocation, sbc_info->sbc.subbands, sbc_info->sbc.blocks, sbc_info->sbc.bitpool);
++    }
+ }
+ /* Run from main thread */
+@@ -1019,7 +1206,7 @@ static int setup_transport(struct userdata *u) {
+     u->transport = t;
+-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
++    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
+         transport_acquire(u, true); /* In case of error, the sink/sources will be created suspended */
+     else if (transport_acquire(u, false) < 0)
+         return -1; /* We need to fail here until the interactions with module-suspend-on-idle and alike get improved */
+@@ -1040,11 +1227,13 @@ static int init_profile(struct userdata *u) {
+     pa_assert(u->transport);
+-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK)
++    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ||
++        u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
+         if (add_sink(u) < 0)
+             r = -1;
+-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
++    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ||
++        u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
+         if (add_source(u) < 0)
+             r = -1;
+@@ -1090,7 +1279,10 @@ static void thread_func(void *userdata) {
+             if (pollfd && (pollfd->revents & POLLIN)) {
+                 int n_read;
+-                n_read = a2dp_process_push(u);
++                if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
++                    n_read = sco_process_push(u);
++                else
++                    n_read = a2dp_process_push(u);
+                 if (n_read < 0)
+                     goto io_fail;
+@@ -1159,8 +1351,13 @@ static void thread_func(void *userdata) {
+                     if (u->write_index <= 0)
+                         u->started_at = pa_rtclock_now();
+-                    if ((n_written = a2dp_process_render(u)) < 0)
+-                        goto io_fail;
++                    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
++                        if ((n_written = a2dp_process_render(u)) < 0)
++                            goto io_fail;
++                    } else {
++                        if ((n_written = sco_process_render(u)) < 0)
++                            goto io_fail;
++                    }
+                     if (n_written == 0)
+                         pa_log("Broken kernel: we got EAGAIN on write() after POLLOUT!");
+@@ -1364,6 +1561,8 @@ static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) {
+     static const pa_direction_t profile_direction[] = {
+         [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT,
+         [PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
++        [PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
++        [PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+         [PA_BLUETOOTH_PROFILE_OFF] = 0
+     };
+@@ -1543,6 +1742,34 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
+         p = PA_CARD_PROFILE_DATA(cp);
+         *p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
++    } else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF)) {
++      /* TODO: Change this profile's name to headset_head_unit, to reflect the remote
++         * device's role and be consistent with the other profiles */
++        cp = pa_card_profile_new("hsp", _("Headset Head Unit (HSP/HFP)"), sizeof(enum profile));
++        cp->priority = 20;
++        cp->n_sinks = 1;
++        cp->n_sources = 1;
++        cp->max_sink_channels = 1;
++        cp->max_source_channels = 1;
++        pa_hashmap_put(input_port->profiles, cp->name, cp);
++        pa_hashmap_put(output_port->profiles, cp->name, cp);
++
++        p = PA_CARD_PROFILE_DATA(cp);
++        *p = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
++    } else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG)) {
++      /* TODO: Change this profile's name to headset_audio_gateway, to reflect the remote
++         * device's role and be consistent with the other profiles */
++        cp = pa_card_profile_new("hfgw", _("Headset Audio Gateway (HSP/HFP)"), sizeof(enum profile));
++        cp->priority = 20;
++        cp->n_sinks = 1;
++        cp->n_sources = 1;
++        cp->max_sink_channels = 1;
++        cp->max_source_channels = 1;
++        pa_hashmap_put(input_port->profiles, cp->name, cp);
++        pa_hashmap_put(output_port->profiles, cp->name, cp);
++
++        p = PA_CARD_PROFILE_DATA(cp);
++        *p = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY;
+     }
+     if (cp && u->device->transports[*p])
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0011-bluetooth-Create-Handsfree-Audio-Agent-NULL-backend.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0011-bluetooth-Create-Handsfree-Audio-Agent-NULL-backend.patch
new file mode 100644 (file)
index 0000000..0ead373
--- /dev/null
@@ -0,0 +1,176 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Tue, 9 Jul 2013 19:08:15 -0300
+Subject: bluetooth: Create Handsfree Audio Agent NULL backend
+
+Change-Id: I1f562a336f2cd2d0ab91c25fd5128da3cc7fe6df
+---
+ configure.ac                              | 10 +++++++++
+ src/Makefile.am                           |  4 +++-
+ src/modules/bluetooth/bluez5-util.c       |  6 +++++
+ src/modules/bluetooth/hfaudioagent-null.c | 37 +++++++++++++++++++++++++++++++
+ src/modules/bluetooth/hfaudioagent.h      | 31 ++++++++++++++++++++++++++
+ 5 files changed, 87 insertions(+), 1 deletion(-)
+ create mode 100644 src/modules/bluetooth/hfaudioagent-null.c
+ create mode 100644 src/modules/bluetooth/hfaudioagent.h
+
+diff --git a/configure.ac b/configure.ac
+index 4e9f97e..a35918c 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -1022,6 +1022,15 @@ AS_IF([test "x$HAVE_BLUEZ_4" = "x1" || test "x$HAVE_BLUEZ_5" = "x1"], HAVE_BLUEZ
+ AC_SUBST(HAVE_BLUEZ)
+ AM_CONDITIONAL([HAVE_BLUEZ], [test "x$HAVE_BLUEZ" = x1])
++## Headset profiles backend ##
++AC_ARG_WITH(bluetooth_headset_backend, AS_HELP_STRING([--with-bluetooth-headset-backend=<null|ofono>],[Backend for Bluetooth headset profiles (null)]))
++if test -z "$with_bluetooth_headset_backend" ; then
++    BLUETOOTH_HEADSET_BACKEND=null
++else
++    BLUETOOTH_HEADSET_BACKEND=$with_bluetooth_headset_backend
++fi
++AC_SUBST(BLUETOOTH_HEADSET_BACKEND)
++
+ #### UDEV support (optional) ####
+ AC_ARG_ENABLE([udev],
+@@ -1486,6 +1495,7 @@ echo "
+     Enable D-Bus:                  ${ENABLE_DBUS}
+       Enable BlueZ 4:              ${ENABLE_BLUEZ_4}
+       Enable BlueZ 5:              ${ENABLE_BLUEZ_5}
++        headset backed:            ${BLUETOOTH_HEADSET_BACKEND}
+     Enable udev:                   ${ENABLE_UDEV}
+       Enable HAL->udev compat:     ${ENABLE_HAL_COMPAT}
+     Enable systemd login:          ${ENABLE_SYSTEMD}
+diff --git a/src/Makefile.am b/src/Makefile.am
+index e1808e6..2edce5f 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -2074,7 +2074,9 @@ module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
+ libbluez5_util_la_SOURCES = \
+               modules/bluetooth/bluez5-util.c \
+               modules/bluetooth/bluez5-util.h \
+-              modules/bluetooth/a2dp-codecs.h
++              modules/bluetooth/a2dp-codecs.h \
++              modules/bluetooth/hfaudioagent.h \
++              modules/bluetooth/hfaudioagent-@BLUETOOTH_HEADSET_BACKEND@.c
+ libbluez5_util_la_LDFLAGS = -avoid-version
+ libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
+ libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
+index ae219ea..35c8adf 100644
+--- a/src/modules/bluetooth/bluez5-util.c
++++ b/src/modules/bluetooth/bluez5-util.c
+@@ -34,6 +34,7 @@
+ #include <pulsecore/shared.h>
+ #include "a2dp-codecs.h"
++#include "hfaudioagent.h"
+ #include "bluez5-util.h"
+@@ -87,6 +88,7 @@ struct pa_bluetooth_discovery {
+     pa_hashmap *devices;
+     pa_hashmap *transports;
++    hf_audio_agent_data *hf_audio_agent;
+     PA_LLIST_HEAD(pa_dbus_pending, pending);
+ };
+@@ -1574,6 +1576,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
+     endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
+     endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
++    y->hf_audio_agent = hf_audio_agent_init(c);
+     get_managed_objects(y);
+@@ -1615,6 +1618,9 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
+         pa_hashmap_free(y->transports);
+     }
++    if (y->hf_audio_agent)
++        hf_audio_agent_done(y->hf_audio_agent);
++
+     if (y->connection) {
+         if (y->matches_added)
+diff --git a/src/modules/bluetooth/hfaudioagent-null.c b/src/modules/bluetooth/hfaudioagent-null.c
+new file mode 100644
+index 0000000..96fca06
+--- /dev/null
++++ b/src/modules/bluetooth/hfaudioagent-null.c
+@@ -0,0 +1,37 @@
++/***
++  This file is part of PulseAudio.
++
++  Copyright 2013 João Paulo Rechi Vita
++
++  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 <pulsecore/log.h>
++
++#include "hfaudioagent.h"
++
++hf_audio_agent_data *hf_audio_agent_init(pa_core *c) {
++    pa_log_debug("HandsfreeAudioAgent API support disabled");
++    return NULL;
++}
++
++void hf_audio_agent_done(hf_audio_agent_data *data) {
++    /* Nothing to do here */
++}
+diff --git a/src/modules/bluetooth/hfaudioagent.h b/src/modules/bluetooth/hfaudioagent.h
+new file mode 100644
+index 0000000..2982034
+--- /dev/null
++++ b/src/modules/bluetooth/hfaudioagent.h
+@@ -0,0 +1,31 @@
++#ifndef foohfagenthfoo
++#define foohfagenthfoo
++
++/***
++  This file is part of PulseAudio.
++
++  Copyright 2013 João Paulo Rechi Vita
++
++  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 hf_audio_agent_data hf_audio_agent_data;
++
++hf_audio_agent_data *hf_audio_agent_init(pa_core *c);
++void hf_audio_agent_done(hf_audio_agent_data *data);
++#endif
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0012-bluetooth-Create-Handsfree-Audio-Agent-oFono-backend.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0012-bluetooth-Create-Handsfree-Audio-Agent-oFono-backend.patch
new file mode 100644 (file)
index 0000000..39b938b
--- /dev/null
@@ -0,0 +1,161 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Tue, 9 Jul 2013 20:22:17 -0300
+Subject: bluetooth: Create Handsfree Audio Agent oFono backend
+
+Change-Id: I2d5793e997205a04b37b9389ab75fc8643adcece
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 145 +++++++++++++++++++++++++++++
+ 1 file changed, 145 insertions(+)
+ create mode 100644 src/modules/bluetooth/hfaudioagent-ofono.c
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+new file mode 100644
+index 0000000..af78d4d
+--- /dev/null
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -0,0 +1,145 @@
++/***
++  This file is part of PulseAudio.
++
++  Copyright 2013 João Paulo Rechi Vita
++
++  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 <pulsecore/core-util.h>
++#include <pulsecore/dbus-shared.h>
++
++#include "hfaudioagent.h"
++
++#define OFONO_SERVICE "org.ofono"
++#define HF_AUDIO_AGENT_INTERFACE OFONO_SERVICE ".HandsfreeAudioAgent"
++
++#define HF_AUDIO_AGENT_PATH "/HandsfreeAudioAgent"
++
++#define HF_AUDIO_AGENT_XML                                          \
++    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
++    "<node>"                                                        \
++    "  <interface name=\"org.freedesktop.DBus.Introspectable\">"    \
++    "    <method name=\"Introspect\">"                              \
++    "      <arg direction=\"out\" type=\"s\" />"                    \
++    "    </method>"                                                 \
++    "  </interface>"                                                \
++    "  <interface name=\"org.ofono.HandsfreeAudioAgent\">"          \
++    "    <method name=\"Release\">"                                 \
++    "    </method>"                                                 \
++    "    <method name=\"NewConnection\">"                           \
++    "      <arg direction=\"in\"  type=\"o\" name=\"card_path\" />" \
++    "      <arg direction=\"in\"  type=\"h\" name=\"sco_fd\" />"    \
++    "      <arg direction=\"in\"  type=\"y\" name=\"codec\" />"     \
++    "    </method>"                                                 \
++    "  </interface>"                                                \
++    "</node>"
++
++struct hf_audio_agent_data {
++    pa_core *core;
++    pa_dbus_connection *connection;
++};
++
++static DBusMessage *hf_audio_agent_release(DBusConnection *c, DBusMessage *m, void *data) {
++    DBusMessage *r = dbus_message_new_error(m, "org.ofono.Error.NotImplemented", "Operation is not implemented");
++    return r;
++}
++
++static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage *m, void *data) {
++    DBusMessage *r = dbus_message_new_error(m, "org.ofono.Error.NotImplemented", "Operation is not implemented");
++    return r;
++}
++
++static DBusHandlerResult hf_audio_agent_handler(DBusConnection *c, DBusMessage *m, void *data) {
++    hf_audio_agent_data *hfdata = data;
++    DBusMessage *r = NULL;
++    const char *path, *interface, *member;
++
++    pa_assert(hfdata);
++
++    path = dbus_message_get_path(m);
++    interface = dbus_message_get_interface(m);
++    member = dbus_message_get_member(m);
++
++    if (!pa_streq(path, HF_AUDIO_AGENT_PATH))
++        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
++
++    pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
++
++    if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
++        const char *xml = HF_AUDIO_AGENT_XML;
++
++        pa_assert_se(r = dbus_message_new_method_return(m));
++        pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
++
++    } else if (dbus_message_is_method_call(m, HF_AUDIO_AGENT_INTERFACE, "NewConnection"))
++        r = hf_audio_agent_new_connection(c, m, data);
++    else if (dbus_message_is_method_call(m, HF_AUDIO_AGENT_INTERFACE, "Release"))
++        r = hf_audio_agent_release(c, m, data);
++    else
++        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
++
++    if (r) {
++        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(hfdata->connection), r, NULL));
++        dbus_message_unref(r);
++    }
++
++    return DBUS_HANDLER_RESULT_HANDLED;
++}
++
++hf_audio_agent_data *hf_audio_agent_init(pa_core *c) {
++    hf_audio_agent_data *hfdata;
++    DBusError err;
++    static const DBusObjectPathVTable vtable_hf_audio_agent = {
++        .message_function = hf_audio_agent_handler,
++    };
++
++    pa_assert(c);
++
++    hfdata = pa_xnew0(hf_audio_agent_data, 1);
++    hfdata->core = c;
++
++    dbus_error_init(&err);
++
++    if (!(hfdata->connection = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &err))) {
++        pa_log("Failed to get D-Bus connection: %s", err.message);
++        dbus_error_free(&err);
++        return NULL;
++    }
++
++    pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(hfdata->connection), HF_AUDIO_AGENT_PATH,
++                                                      &vtable_hf_audio_agent, hfdata));
++
++    return hfdata;
++}
++
++void hf_audio_agent_done(hf_audio_agent_data *data) {
++    hf_audio_agent_data *hfdata = data;
++
++    pa_assert(hfdata);
++
++    if (hfdata->connection) {
++        dbus_connection_unregister_object_path(pa_dbus_connection_get(hfdata->connection), HF_AUDIO_AGENT_PATH);
++
++        pa_dbus_connection_unref(hfdata->connection);
++    }
++
++    pa_xfree(hfdata);
++}
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0013-bluetooth-Monitor-D-Bus-signals.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0013-bluetooth-Monitor-D-Bus-signals.patch
new file mode 100644 (file)
index 0000000..0a93d5c
--- /dev/null
@@ -0,0 +1,99 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Wed, 10 Jul 2013 12:18:07 -0300
+Subject: bluetooth: Monitor D-Bus signals
+
+Change-Id: Ibfd4ab9acaf49df0fdfaff55609c560ab31f4df4
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 48 ++++++++++++++++++++++++++++++
+ 1 file changed, 48 insertions(+)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index af78d4d..0e3ffc4 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -30,6 +30,7 @@
+ #define OFONO_SERVICE "org.ofono"
+ #define HF_AUDIO_AGENT_INTERFACE OFONO_SERVICE ".HandsfreeAudioAgent"
++#define HF_AUDIO_MANAGER_INTERFACE OFONO_SERVICE ".HandsfreeAudioManager"
+ #define HF_AUDIO_AGENT_PATH "/HandsfreeAudioAgent"
+@@ -55,8 +56,18 @@
+ struct hf_audio_agent_data {
+     pa_core *core;
+     pa_dbus_connection *connection;
++
++    bool filter_added;
++    pa_hashmap *hf_audio_cards;
+ };
++static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *data) {
++    pa_assert(bus);
++    pa_assert(m);
++
++    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
++}
++
+ static DBusMessage *hf_audio_agent_release(DBusConnection *c, DBusMessage *m, void *data) {
+     DBusMessage *r = dbus_message_new_error(m, "org.ofono.Error.NotImplemented", "Operation is not implemented");
+     return r;
+@@ -115,6 +126,7 @@ hf_audio_agent_data *hf_audio_agent_init(pa_core *c) {
+     hfdata = pa_xnew0(hf_audio_agent_data, 1);
+     hfdata->core = c;
++    hfdata->hf_audio_cards = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+     dbus_error_init(&err);
+@@ -124,6 +136,25 @@ hf_audio_agent_data *hf_audio_agent_init(pa_core *c) {
+         return NULL;
+     }
++    /* dynamic detection of handsfree audio cards */
++    if (!dbus_connection_add_filter(pa_dbus_connection_get(hfdata->connection), filter_cb, hfdata, NULL)) {
++        pa_log_error("Failed to add filter function");
++        hf_audio_agent_done(hfdata);
++        return NULL;
++    }
++    hfdata->filter_added = true;
++
++    if (pa_dbus_add_matches(pa_dbus_connection_get(hfdata->connection), &err,
++            "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',"
++            "arg0='" OFONO_SERVICE "'",
++            "type='signal',sender='" OFONO_SERVICE "',interface='" HF_AUDIO_MANAGER_INTERFACE "',member='CardAdded'",
++            "type='signal',sender='" OFONO_SERVICE "',interface='" HF_AUDIO_MANAGER_INTERFACE "',member='CardRemoved'",
++            NULL) < 0) {
++        pa_log("Failed to add oFono D-Bus matches: %s", err.message);
++        hf_audio_agent_done(hfdata);
++        return NULL;
++    }
++
+     pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(hfdata->connection), HF_AUDIO_AGENT_PATH,
+                                                       &vtable_hf_audio_agent, hfdata));
+@@ -135,7 +166,24 @@ void hf_audio_agent_done(hf_audio_agent_data *data) {
+     pa_assert(hfdata);
++    if (hfdata->hf_audio_cards) {
++        pa_hashmap_free(hfdata->hf_audio_cards, NULL);
++        hfdata->hf_audio_cards = NULL;
++    }
++
+     if (hfdata->connection) {
++
++        pa_dbus_remove_matches(
++            pa_dbus_connection_get(hfdata->connection),
++            "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',"
++            "arg0='" OFONO_SERVICE "'",
++            "type='signal',sender='" OFONO_SERVICE "',interface='" HF_AUDIO_MANAGER_INTERFACE "',member='CardAdded'",
++            "type='signal',sender='" OFONO_SERVICE "',interface='" HF_AUDIO_MANAGER_INTERFACE "',member='CardRemoved'",
++            NULL);
++
++        if (hfdata->filter_added)
++            dbus_connection_remove_filter(pa_dbus_connection_get(hfdata->connection), filter_cb, hfdata);
++
+         dbus_connection_unregister_object_path(pa_dbus_connection_get(hfdata->connection), HF_AUDIO_AGENT_PATH);
+         pa_dbus_connection_unref(hfdata->connection);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0014-bluetooth-Create-pa_bluetooth_dbus_send_and_add_to_p.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0014-bluetooth-Create-pa_bluetooth_dbus_send_and_add_to_p.patch
new file mode 100644 (file)
index 0000000..c9d893c
--- /dev/null
@@ -0,0 +1,51 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Wed, 10 Jul 2013 13:00:16 -0300
+Subject: bluetooth: Create pa_bluetooth_dbus_send_and_add_to_pending() for
+ oFono backend
+
+Change-Id: I9b6ea22d6bd1059e5d7b3a12ec13b2768cacce0d
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 21 +++++++++++++++++++++
+ 1 file changed, 21 insertions(+)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index 0e3ffc4..38975b2 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -59,8 +59,27 @@ struct hf_audio_agent_data {
+     bool filter_added;
+     pa_hashmap *hf_audio_cards;
++
++    PA_LLIST_HEAD(pa_dbus_pending, pending);
+ };
++static pa_dbus_pending* pa_bluetooth_dbus_send_and_add_to_pending(hf_audio_agent_data *hfdata, DBusMessage *m,
++                                                                  DBusPendingCallNotifyFunction func, void *call_data) {
++    pa_dbus_pending *p;
++    DBusPendingCall *call;
++
++    pa_assert(hfdata);
++    pa_assert(m);
++
++    pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(hfdata->connection), m, &call, -1));
++
++    p = pa_dbus_pending_new(pa_dbus_connection_get(hfdata->connection), m, call, hfdata, call_data);
++    PA_LLIST_PREPEND(pa_dbus_pending, hfdata->pending, p);
++    dbus_pending_call_set_notify(call, func, p, NULL);
++
++    return p;
++}
++
+ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *data) {
+     pa_assert(bus);
+     pa_assert(m);
+@@ -166,6 +185,8 @@ void hf_audio_agent_done(hf_audio_agent_data *data) {
+     pa_assert(hfdata);
++    pa_dbus_free_pending_list(&hfdata->pending);
++
+     if (hfdata->hf_audio_cards) {
+         pa_hashmap_free(hfdata->hf_audio_cards, NULL);
+         hfdata->hf_audio_cards = NULL;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0015-bluetooth-Register-Unregister-Handsfree-Audio-Agent-.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0015-bluetooth-Register-Unregister-Handsfree-Audio-Agent-.patch
new file mode 100644 (file)
index 0000000..931069c
--- /dev/null
@@ -0,0 +1,172 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Tue, 9 Jul 2013 20:59:12 -0300
+Subject: bluetooth: Register/Unregister Handsfree Audio Agent with oFono
+
+Register as a HandsfreeAudioAgent with oFono during backend
+initialization and unregiter during backend finalization. This commit
+also adds a check when receiving method calls or signals to make sure
+the sender matches with the D-Bus service we're registered with.
+
+Change-Id: I0a49935702ffb52d6d2ba1baded8eb568262c5b1
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 107 ++++++++++++++++++++++++++++-
+ 1 file changed, 105 insertions(+), 2 deletions(-)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index 38975b2..de58e7d 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -28,6 +28,9 @@
+ #include "hfaudioagent.h"
++#define HFP_AUDIO_CODEC_CVSD    0x01
++#define HFP_AUDIO_CODEC_MSBC    0x02
++
+ #define OFONO_SERVICE "org.ofono"
+ #define HF_AUDIO_AGENT_INTERFACE OFONO_SERVICE ".HandsfreeAudioAgent"
+ #define HF_AUDIO_MANAGER_INTERFACE OFONO_SERVICE ".HandsfreeAudioManager"
+@@ -58,6 +61,7 @@ struct hf_audio_agent_data {
+     pa_dbus_connection *connection;
+     bool filter_added;
++    char *ofono_bus_id;
+     pa_hashmap *hf_audio_cards;
+     PA_LLIST_HEAD(pa_dbus_pending, pending);
+@@ -80,20 +84,115 @@ static pa_dbus_pending* pa_bluetooth_dbus_send_and_add_to_pending(hf_audio_agent
+     return p;
+ }
++static void hf_audio_agent_register_reply(DBusPendingCall *pending, void *userdata) {
++    DBusMessage *r;
++    pa_dbus_pending *p;
++    hf_audio_agent_data *hfdata;
++
++    pa_assert_se(p = userdata);
++    pa_assert_se(hfdata = p->context_data);
++    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
++
++    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
++        pa_log_error("Failed to register as a handsfree audio agent with ofono: %s: %s",
++                     dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
++        goto finish;
++    }
++
++    hfdata->ofono_bus_id = pa_xstrdup(dbus_message_get_sender(r));
++
++    /* TODO: List all HandsfreeAudioCard objects */
++
++finish:
++    dbus_message_unref(r);
++
++    PA_LLIST_REMOVE(pa_dbus_pending, hfdata->pending, p);
++    pa_dbus_pending_free(p);
++}
++
++static void hf_audio_agent_register(hf_audio_agent_data *hfdata) {
++    DBusMessage *m;
++    unsigned char codecs[2];
++    const unsigned char *pcodecs = codecs;
++    int ncodecs = 0;
++    const char *path = HF_AUDIO_AGENT_PATH;
++
++    pa_assert(hfdata);
++
++    pa_assert_se(m = dbus_message_new_method_call(OFONO_SERVICE, "/", HF_AUDIO_MANAGER_INTERFACE, "Register"));
++
++    codecs[ncodecs++] = HFP_AUDIO_CODEC_CVSD;
++    codecs[ncodecs++] = HFP_AUDIO_CODEC_MSBC;
++
++    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pcodecs, ncodecs,
++                                          DBUS_TYPE_INVALID));
++
++    pa_bluetooth_dbus_send_and_add_to_pending(hfdata, m, hf_audio_agent_register_reply, NULL);
++}
++
++static void hf_audio_agent_unregister(hf_audio_agent_data *hfdata) {
++    DBusMessage *m;
++    const char *path = HF_AUDIO_AGENT_PATH;
++
++    pa_assert(hfdata);
++    pa_assert(hfdata->connection);
++
++    if (hfdata->ofono_bus_id) {
++        pa_assert_se(m = dbus_message_new_method_call(hfdata->ofono_bus_id, "/", HF_AUDIO_MANAGER_INTERFACE, "Unregister"));
++        pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID));
++        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(hfdata->connection), m, NULL));
++
++        pa_xfree(hfdata->ofono_bus_id);
++        hfdata->ofono_bus_id = NULL;
++    }
++}
++
+ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *data) {
++    const char *sender;
++    hf_audio_agent_data *hfdata = data;
++
+     pa_assert(bus);
+     pa_assert(m);
++    pa_assert(hfdata);
++
++    sender = dbus_message_get_sender(m);
++    if (!pa_safe_streq(hfdata->ofono_bus_id, sender) && !pa_streq("org.freedesktop.DBus", sender))
++        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ static DBusMessage *hf_audio_agent_release(DBusConnection *c, DBusMessage *m, void *data) {
+-    DBusMessage *r = dbus_message_new_error(m, "org.ofono.Error.NotImplemented", "Operation is not implemented");
++    DBusMessage *r;
++    const char *sender;
++    hf_audio_agent_data *hfdata = data;
++
++    pa_assert(hfdata);
++
++    sender = dbus_message_get_sender(m);
++    if (!pa_streq(hfdata->ofono_bus_id, sender)) {
++        pa_assert_se(r = dbus_message_new_error(m, "org.ofono.Error.NotAllowed", "Operation is not allowed by this sender"));
++        return r;
++    }
++
++    r = dbus_message_new_error(m, "org.ofono.Error.NotImplemented", "Operation is not implemented");
+     return r;
+ }
+ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage *m, void *data) {
+-    DBusMessage *r = dbus_message_new_error(m, "org.ofono.Error.NotImplemented", "Operation is not implemented");
++    DBusMessage *r;
++    const char *sender;
++    hf_audio_agent_data *hfdata = data;
++
++    pa_assert(hfdata);
++
++    sender = dbus_message_get_sender(m);
++    if (!pa_streq(hfdata->ofono_bus_id, sender)) {
++        pa_assert_se(r = dbus_message_new_error(m, "org.ofono.Error.NotAllowed", "Operation is not allowed by this sender"));
++        return r;
++    }
++
++    r = dbus_message_new_error(m, "org.ofono.Error.NotImplemented", "Operation is not implemented");
+     return r;
+ }
+@@ -177,6 +276,8 @@ hf_audio_agent_data *hf_audio_agent_init(pa_core *c) {
+     pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(hfdata->connection), HF_AUDIO_AGENT_PATH,
+                                                       &vtable_hf_audio_agent, hfdata));
++    hf_audio_agent_register(hfdata);
++
+     return hfdata;
+ }
+@@ -205,6 +306,8 @@ void hf_audio_agent_done(hf_audio_agent_data *data) {
+         if (hfdata->filter_added)
+             dbus_connection_remove_filter(pa_dbus_connection_get(hfdata->connection), filter_cb, hfdata);
++        hf_audio_agent_unregister(hfdata);
++
+         dbus_connection_unregister_object_path(pa_dbus_connection_get(hfdata->connection), HF_AUDIO_AGENT_PATH);
+         pa_dbus_connection_unref(hfdata->connection);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0016-bluetooth-List-HandsfreeAudioCard-objects-from-oFono.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0016-bluetooth-List-HandsfreeAudioCard-objects-from-oFono.patch
new file mode 100644 (file)
index 0000000..d943ce4
--- /dev/null
@@ -0,0 +1,98 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Tue, 9 Jul 2013 21:03:28 -0300
+Subject: bluetooth: List HandsfreeAudioCard objects from oFono
+
+Change-Id: Idb2423d8b5640486976df5347e2f7d05a9e6dd71
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 71 +++++++++++++++++++++++++++++-
+ 1 file changed, 70 insertions(+), 1 deletion(-)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index de58e7d..6b27f80 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -84,6 +84,75 @@ static pa_dbus_pending* pa_bluetooth_dbus_send_and_add_to_pending(hf_audio_agent
+     return p;
+ }
++static void hf_audio_agent_get_cards_reply(DBusPendingCall *pending, void *userdata) {
++    DBusMessage *r;
++    pa_dbus_pending *p;
++    hf_audio_agent_data *hfdata;
++    DBusMessageIter i, array_i, struct_i, props_i;
++    char c;
++
++    pa_assert_se(p = userdata);
++    pa_assert_se(hfdata = p->context_data);
++    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
++
++    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
++        pa_log_error("Failed to get a list of handsfree audio cards from ofono: %s: %s",
++                     dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
++        goto finish;
++    }
++
++    dbus_message_iter_init(r, &i);
++    if ((c = dbus_message_iter_get_arg_type(&i)) != DBUS_TYPE_ARRAY) {
++        pa_log_error("Invalid arguments in GetCards() reply: expected \'a\', received \'%c\'", c);
++        goto finish;
++    }
++
++    dbus_message_iter_recurse(&i, &array_i);
++    while (dbus_message_iter_get_arg_type(&array_i) != DBUS_TYPE_INVALID) {
++        const char *path;
++
++        if ((c = dbus_message_iter_get_arg_type(&array_i)) != DBUS_TYPE_STRUCT) {
++            pa_log_error("Invalid arguments in GetCards() reply: expected \'r\', received \'%c\'", c);
++            goto finish;
++        }
++
++        dbus_message_iter_recurse(&array_i, &struct_i);
++        if ((c = dbus_message_iter_get_arg_type(&struct_i)) != DBUS_TYPE_OBJECT_PATH) {
++            pa_log_error("Invalid arguments in GetCards() reply: expected \'o\', received \'%c\'", c);
++            goto finish;
++        }
++
++        dbus_message_iter_get_basic(&struct_i, &path);
++
++        dbus_message_iter_next(&struct_i);
++        if ((c = dbus_message_iter_get_arg_type(&struct_i)) != DBUS_TYPE_ARRAY) {
++            pa_log_error("Invalid arguments in GetCards() reply: expected \'a\', received \'%c\'", c);
++            goto finish;
++        }
++
++        dbus_message_iter_recurse(&struct_i, &props_i);
++
++        /* TODO: Parse HandsfreeAudioCard properties */
++
++        dbus_message_iter_next(&array_i);
++    }
++
++finish:
++    dbus_message_unref(r);
++
++    PA_LLIST_REMOVE(pa_dbus_pending, hfdata->pending, p);
++    pa_dbus_pending_free(p);
++}
++
++static void hf_audio_agent_get_cards(hf_audio_agent_data *hfdata) {
++    DBusMessage *m;
++
++    pa_assert(hfdata);
++
++    pa_assert_se(m = dbus_message_new_method_call(OFONO_SERVICE, "/", HF_AUDIO_MANAGER_INTERFACE, "GetCards"));
++    pa_bluetooth_dbus_send_and_add_to_pending(hfdata, m, hf_audio_agent_get_cards_reply, NULL);
++}
++
+ static void hf_audio_agent_register_reply(DBusPendingCall *pending, void *userdata) {
+     DBusMessage *r;
+     pa_dbus_pending *p;
+@@ -101,7 +170,7 @@ static void hf_audio_agent_register_reply(DBusPendingCall *pending, void *userda
+     hfdata->ofono_bus_id = pa_xstrdup(dbus_message_get_sender(r));
+-    /* TODO: List all HandsfreeAudioCard objects */
++    hf_audio_agent_get_cards(hfdata);
+ finish:
+     dbus_message_unref(r);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0017-bluetooth-Parse-HandsfreeAudioCard-properties.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0017-bluetooth-Parse-HandsfreeAudioCard-properties.patch
new file mode 100644 (file)
index 0000000..f7a2174
--- /dev/null
@@ -0,0 +1,192 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Tue, 9 Jul 2013 21:37:26 -0300
+Subject: bluetooth: Parse HandsfreeAudioCard properties
+
+Change-Id: I37600ca3ab60d1b82608f4e395fa427fdaf62164
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 128 ++++++++++++++++++++++++++++-
+ 1 file changed, 126 insertions(+), 2 deletions(-)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index 6b27f80..2e16a48 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -25,6 +25,9 @@
+ #include <pulsecore/core-util.h>
+ #include <pulsecore/dbus-shared.h>
++#include <pulsecore/shared.h>
++
++#include "bluez5-util.h"
+ #include "hfaudioagent.h"
+@@ -56,9 +59,21 @@
+     "  </interface>"                                                \
+     "</node>"
++typedef struct hf_audio_card {
++    char *path;
++    char *remote;
++    char *local;
++
++    int fd;
++    uint8_t codec;
++
++    pa_bluetooth_transport *transport;
++} hf_audio_card;
++
+ struct hf_audio_agent_data {
+     pa_core *core;
+     pa_dbus_connection *connection;
++    pa_bluetooth_discovery *discovery;
+     bool filter_added;
+     char *ofono_bus_id;
+@@ -84,6 +99,111 @@ static pa_dbus_pending* pa_bluetooth_dbus_send_and_add_to_pending(hf_audio_agent
+     return p;
+ }
++static hf_audio_card *hf_audio_card_new(hf_audio_agent_data *hfdata, const char *path) {
++    hf_audio_card *hfac = pa_xnew0(hf_audio_card, 1);
++
++    hfac->path = pa_xstrdup(path);
++    hfac->fd = -1;
++
++    return hfac;
++}
++
++static void hf_audio_card_free(void *data) {
++    hf_audio_card *hfac = data;
++
++    pa_assert(hfac);
++
++    pa_bluetooth_transport_free(hfac->transport);
++    pa_xfree(hfac->path);
++    pa_xfree(hfac->remote);
++    pa_xfree(hfac->local);
++    pa_xfree(hfac);
++}
++
++static int hf_audio_agent_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
++    return -1;
++}
++
++static void hf_audio_agent_transport_release(pa_bluetooth_transport *t) {
++}
++
++static void hf_audio_agent_card_found(hf_audio_agent_data *hfdata, const char *path, DBusMessageIter *props_i) {
++    DBusMessageIter i, value_i;
++    const char *key, *value;
++    hf_audio_card *hfac;
++    pa_bluetooth_device *d;
++
++    pa_assert(hfdata);
++    pa_assert(path);
++    pa_assert(props_i);
++
++    pa_log_debug("New HF card found: %s", path);
++
++    hfac = hf_audio_card_new(hfdata, path);
++
++    while (dbus_message_iter_get_arg_type(props_i) != DBUS_TYPE_INVALID) {
++        char c;
++
++        if ((c = dbus_message_iter_get_arg_type(props_i)) != DBUS_TYPE_DICT_ENTRY) {
++            pa_log_error("Invalid properties for %s: expected \'e\', received \'%c\'", path, c);
++            goto fail;
++        }
++
++        dbus_message_iter_recurse(props_i, &i);
++
++        if ((c = dbus_message_iter_get_arg_type(&i)) != DBUS_TYPE_STRING) {
++            pa_log_error("Invalid properties for %s: expected \'s\', received \'%c\'", path, c);
++            goto fail;
++        }
++
++        dbus_message_iter_get_basic(&i, &key);
++        dbus_message_iter_next(&i);
++
++        if ((c = dbus_message_iter_get_arg_type(&i)) != DBUS_TYPE_VARIANT) {
++            pa_log_error("Invalid properties for %s: expected \'v\', received \'%c\'", path, c);
++            goto fail;
++        }
++
++        dbus_message_iter_recurse(&i, &value_i);
++
++        if ((c = dbus_message_iter_get_arg_type(&value_i)) != DBUS_TYPE_STRING) {
++            pa_log_error("Invalid properties for %s: expected \'s\', received \'%c\'", path, c);
++            goto fail;
++        }
++
++        dbus_message_iter_get_basic(&value_i, &value);
++
++        if (pa_streq(key, "RemoteAddress"))
++            hfac->remote = pa_xstrdup(value);
++        else if (pa_streq(key, "LocalAddress"))
++            hfac->local = pa_xstrdup(value);
++
++        pa_log_debug("%s: %s", key, value);
++
++        dbus_message_iter_next(props_i);
++    }
++
++    pa_hashmap_put(hfdata->hf_audio_cards, hfac->path, hfac);
++
++    d = pa_bluetooth_discovery_get_device_by_address(hfdata->discovery, hfac->remote, hfac->local);
++    if (d) {
++        hfac->transport = pa_bluetooth_transport_new(d, hfdata->ofono_bus_id, path, PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY, NULL, 0);
++        hfac->transport->acquire = hf_audio_agent_transport_acquire;
++        hfac->transport->release = hf_audio_agent_transport_release;
++        hfac->transport->userdata = hfdata;
++
++        d->transports[PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = hfac->transport;
++
++        pa_bluetooth_transport_put(hfac->transport);
++    } else
++        pa_log_error("Device doesnt exist for %s", path);
++
++    return;
++
++fail:
++    pa_xfree(hfac);
++}
++
+ static void hf_audio_agent_get_cards_reply(DBusPendingCall *pending, void *userdata) {
+     DBusMessage *r;
+     pa_dbus_pending *p;
+@@ -132,7 +252,7 @@ static void hf_audio_agent_get_cards_reply(DBusPendingCall *pending, void *userd
+         dbus_message_iter_recurse(&struct_i, &props_i);
+-        /* TODO: Parse HandsfreeAudioCard properties */
++        hf_audio_agent_card_found(hfdata, path, &props_i);
+         dbus_message_iter_next(&array_i);
+     }
+@@ -314,6 +434,7 @@ hf_audio_agent_data *hf_audio_agent_init(pa_core *c) {
+     hfdata = pa_xnew0(hf_audio_agent_data, 1);
+     hfdata->core = c;
+     hfdata->hf_audio_cards = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
++    hfdata->discovery = pa_shared_get(c, "bluetooth-discovery");
+     dbus_error_init(&err);
+@@ -358,7 +479,7 @@ void hf_audio_agent_done(hf_audio_agent_data *data) {
+     pa_dbus_free_pending_list(&hfdata->pending);
+     if (hfdata->hf_audio_cards) {
+-        pa_hashmap_free(hfdata->hf_audio_cards, NULL);
++        pa_hashmap_free(hfdata->hf_audio_cards, hf_audio_card_free);
+         hfdata->hf_audio_cards = NULL;
+     }
+@@ -382,5 +503,8 @@ void hf_audio_agent_done(hf_audio_agent_data *data) {
+         pa_dbus_connection_unref(hfdata->connection);
+     }
++    if (hfdata->discovery)
++        hfdata->discovery = NULL;
++
+     pa_xfree(hfdata);
+ }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0018-bluetooth-Implement-transport-acquire-for-hf_audio_a.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0018-bluetooth-Implement-transport-acquire-for-hf_audio_a.patch
new file mode 100644 (file)
index 0000000..c3815cc
--- /dev/null
@@ -0,0 +1,48 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Tue, 9 Jul 2013 22:22:28 -0300
+Subject: bluetooth: Implement transport acquire for hf_audio_agent transports
+
+Change-Id: Ia092e75c51f79d1a594ba6dacda55ec03a5dae93
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 28 +++++++++++++++++++++++++++-
+ 1 file changed, 27 insertions(+), 1 deletion(-)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index 2e16a48..3684bed 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -121,7 +121,33 @@ static void hf_audio_card_free(void *data) {
+ }
+ static int hf_audio_agent_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
+-    return -1;
++    hf_audio_agent_data *hfdata = t->userdata;
++    hf_audio_card *hfac = pa_hashmap_get(hfdata->hf_audio_cards, t->path);
++
++    if (!optional) {
++        DBusMessage *m;
++
++        pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.ofono.HandsfreeAudioCard", "Connect"));
++        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(hfdata->connection), m, NULL));
++
++        return -1;
++    }
++
++    /* The correct block size should take into account the SCO MTU from
++     * the Bluetooth adapter and (for adapters in the USB bus) the MxPS
++     * value from the Isoc USB endpoint in use by btusb and should be
++     * made available to userspace by the Bluetooth kernel subsystem.
++     * Meanwhile the empiric value 48 will be used. */
++    if (imtu)
++        *imtu = 48;
++    if (omtu)
++        *omtu = 48;
++
++    if (hfac) {
++        t->codec = hfac->codec;
++        return hfac->fd;
++    } else
++        return -1;
+ }
+ static void hf_audio_agent_transport_release(pa_bluetooth_transport *t) {
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0019-bluetooth-Track-oFono-service.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0019-bluetooth-Track-oFono-service.patch
new file mode 100644 (file)
index 0000000..5f8fa21
--- /dev/null
@@ -0,0 +1,109 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Thu, 11 Jul 2013 11:43:48 -0300
+Subject: bluetooth: Track oFono service
+
+Change-Id: I6752f21b848757dbea20c92c68b581141cefbc67
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 61 ++++++++++++++++++++++++++++--
+ 1 file changed, 58 insertions(+), 3 deletions(-)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index 3684bed..97e0fa8 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -364,6 +364,7 @@ static void hf_audio_agent_unregister(hf_audio_agent_data *hfdata) {
+ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *data) {
+     const char *sender;
++    DBusError err;
+     hf_audio_agent_data *hfdata = data;
+     pa_assert(bus);
+@@ -374,6 +375,46 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *da
+     if (!pa_safe_streq(hfdata->ofono_bus_id, sender) && !pa_streq("org.freedesktop.DBus", sender))
+         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
++    dbus_error_init(&err);
++
++    if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
++        const char *name, *old_owner, *new_owner;
++
++        if (!dbus_message_get_args(m, &err,
++                                   DBUS_TYPE_STRING, &name,
++                                   DBUS_TYPE_STRING, &old_owner,
++                                   DBUS_TYPE_STRING, &new_owner,
++                                   DBUS_TYPE_INVALID)) {
++            pa_log_error("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
++            goto fail;
++        }
++
++        if (pa_streq(name, OFONO_SERVICE)) {
++
++            if (old_owner && *old_owner) {
++                pa_log_debug("oFono disappeared");
++
++                if (hfdata->hf_audio_cards) {
++                    pa_hashmap_free(hfdata->hf_audio_cards);
++                    hfdata->hf_audio_cards = NULL;
++                }
++
++                if(hfdata->ofono_bus_id) {
++                    pa_xfree(hfdata->ofono_bus_id);
++                    hfdata->ofono_bus_id = NULL;
++                }
++            }
++
++            if (new_owner && *new_owner) {
++                pa_log_debug("oFono appeared");
++                hf_audio_agent_register(hfdata);
++            }
++        }
++
++    }
++
++fail:
++    dbus_error_free(&err);
+     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+@@ -390,7 +431,20 @@ static DBusMessage *hf_audio_agent_release(DBusConnection *c, DBusMessage *m, vo
+         return r;
+     }
+-    r = dbus_message_new_error(m, "org.ofono.Error.NotImplemented", "Operation is not implemented");
++    pa_log_debug("HF audio agent has been unregistered by oFono (%s)", hfdata->ofono_bus_id);
++
++    if (hfdata->hf_audio_cards) {
++        pa_hashmap_free(hfdata->hf_audio_cards);
++        hfdata->hf_audio_cards = NULL;
++    }
++
++    if(hfdata->ofono_bus_id) {
++        pa_xfree(hfdata->ofono_bus_id);
++        hfdata->ofono_bus_id = NULL;
++    }
++
++    pa_assert_se(r = dbus_message_new_method_return(m));
++
+     return r;
+ }
+@@ -459,7 +513,8 @@ hf_audio_agent_data *hf_audio_agent_init(pa_core *c) {
+     hfdata = pa_xnew0(hf_audio_agent_data, 1);
+     hfdata->core = c;
+-    hfdata->hf_audio_cards = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
++    hfdata->hf_audio_cards = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
++                                                 NULL, hf_audio_card_free);
+     hfdata->discovery = pa_shared_get(c, "bluetooth-discovery");
+     dbus_error_init(&err);
+@@ -505,7 +560,7 @@ void hf_audio_agent_done(hf_audio_agent_data *data) {
+     pa_dbus_free_pending_list(&hfdata->pending);
+     if (hfdata->hf_audio_cards) {
+-        pa_hashmap_free(hfdata->hf_audio_cards, hf_audio_card_free);
++        pa_hashmap_free(hfdata->hf_audio_cards);
+         hfdata->hf_audio_cards = NULL;
+     }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0020-bluetooth-Handle-CardAdded-signal.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0020-bluetooth-Handle-CardAdded-signal.patch
new file mode 100644 (file)
index 0000000..f3aaf33
--- /dev/null
@@ -0,0 +1,38 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Thu, 11 Jul 2013 11:47:37 -0300
+Subject: bluetooth: Handle CardAdded signal
+
+Change-Id: I695b97e26ce369d76503980c73fc3849252b4c91
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index 97e0fa8..be20257 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -411,6 +411,24 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *da
+             }
+         }
++    } else if (dbus_message_is_signal(m, "org.ofono.HandsfreeAudioManager", "CardAdded")) {
++        const char *p;
++        DBusMessageIter arg_i, props_i;
++
++        if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) {
++            pa_log_error("Failed to parse org.ofono.HandsfreeAudioManager.CardAdded");
++            goto fail;
++        }
++
++        dbus_message_iter_get_basic(&arg_i, &p);
++
++        pa_assert_se(dbus_message_iter_next(&arg_i));
++        pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
++
++        dbus_message_iter_recurse(&arg_i, &props_i);
++
++        hf_audio_agent_card_found(hfdata, p, &props_i);
++
+     }
+ fail:
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0021-bluetooth-Handle-CardRemoved-signal.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0021-bluetooth-Handle-CardRemoved-signal.patch
new file mode 100644 (file)
index 0000000..fbd766e
--- /dev/null
@@ -0,0 +1,43 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Thu, 11 Jul 2013 12:53:10 -0300
+Subject: bluetooth: Handle CardRemoved signal
+
+Change-Id: I41083b3b5d8a318927dcdb373e871032c3f52748
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 23 +++++++++++++++++++++++
+ 1 file changed, 23 insertions(+)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index be20257..ee158af 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -429,6 +429,29 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *da
+         hf_audio_agent_card_found(hfdata, p, &props_i);
++    } else if (dbus_message_is_signal(m, "org.ofono.HandsfreeAudioManager", "CardRemoved")) {
++        const char *p;
++        hf_audio_card *hfac;
++        bool old_any_connected;
++
++        if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &p, DBUS_TYPE_INVALID)) {
++            pa_log_error("Failed to parse org.ofono.HandsfreeAudioManager.CardRemoved: %s", err.message);
++            goto fail;
++        }
++
++        if ((hfac = pa_hashmap_remove(hfdata->hf_audio_cards, p)) != NULL) {
++            old_any_connected = pa_bluetooth_device_any_transport_connected(hfac->transport->device);
++
++            hfac->transport->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
++            hfac->transport->device->transports[hfac->transport->profile] = NULL;
++            pa_hook_fire(pa_bluetooth_discovery_hook(hfdata->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED), hfac->transport);
++
++            if (old_any_connected != pa_bluetooth_device_any_transport_connected(hfac->transport->device)) {
++                pa_hook_fire(pa_bluetooth_discovery_hook(hfdata->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED), hfac->transport->device);
++            }
++
++            hf_audio_card_free(hfac);
++        }
+     }
+ fail:
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0022-bluetooth-Implement-org.ofono.HandsfreeAudioAgent.Ne.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0022-bluetooth-Implement-org.ofono.HandsfreeAudioAgent.Ne.patch
new file mode 100644 (file)
index 0000000..4ad62dc
--- /dev/null
@@ -0,0 +1,56 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Thu, 11 Jul 2013 13:23:31 -0300
+Subject: bluetooth: Implement org.ofono.HandsfreeAudioAgent.NewConnection()
+
+Change-Id: Idaff2e3a96ce83e7ee6f961543273429b044be8e
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 29 +++++++++++++++++++++++++++--
+ 1 file changed, 27 insertions(+), 2 deletions(-)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index ee158af..ba0acaf 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -491,7 +491,10 @@ static DBusMessage *hf_audio_agent_release(DBusConnection *c, DBusMessage *m, vo
+ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage *m, void *data) {
+     DBusMessage *r;
+-    const char *sender;
++    const char *sender, *card;
++    int fd;
++    uint8_t codec;
++    hf_audio_card *hfac;
+     hf_audio_agent_data *hfdata = data;
+     pa_assert(hfdata);
+@@ -502,7 +505,29 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage
+         return r;
+     }
+-    r = dbus_message_new_error(m, "org.ofono.Error.NotImplemented", "Operation is not implemented");
++    if (dbus_message_get_args(m, NULL,
++                              DBUS_TYPE_OBJECT_PATH, &card,
++                              DBUS_TYPE_UNIX_FD, &fd,
++                              DBUS_TYPE_BYTE, &codec,
++                              DBUS_TYPE_INVALID) == FALSE) {
++        pa_assert_se(r = dbus_message_new_error(m, "org.ofono.Error.InvalidArguments", "Invalid arguments in method call"));
++        return r;
++    }
++
++    if ( !(hfac = pa_hashmap_get(hfdata->hf_audio_cards, card)) ) {
++        pa_log_warn("New audio connection on unknown card %s (fd=%d, codec=%d)", card, fd, codec);
++        pa_assert_se(r = dbus_message_new_error(m, "org.ofono.Error.InvalidArguments", "Unknown card"));
++        return r;
++    } else
++        pa_log_debug("New audio connection on card %s (fd=%d, codec=%d)", card, fd, codec);
++
++    hfac->fd = fd;
++    hfac->codec = codec;
++    hfac->transport->state = PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
++    pa_hook_fire(pa_bluetooth_discovery_hook(hfdata->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED), hfac->transport);
++
++    pa_assert_se(r = dbus_message_new_method_return(m));
++
+     return r;
+ }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0023-bluetooth-Fix-not-handle-fd-in-DEFER_SETUP-state.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0023-bluetooth-Fix-not-handle-fd-in-DEFER_SETUP-state.patch
new file mode 100644 (file)
index 0000000..587fce6
--- /dev/null
@@ -0,0 +1,44 @@
+From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Date: Fri, 23 Aug 2013 12:56:40 +0300
+Subject: bluetooth: Fix not handle fd in DEFER_SETUP state
+
+The fd passed over NewConnection is in DEFER_SETUP and need to be read to
+be accept otherwise it wont work.
+
+Change-Id: I2f6df033d3c1602064a39bb40a5bbd60e014c8f7
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 14 ++++++++++++--
+ 1 file changed, 12 insertions(+), 2 deletions(-)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index ba0acaf..90d1348 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -23,6 +23,8 @@
+ #include <config.h>
+ #endif
++#include <errno.h>
++
+ #include <pulsecore/core-util.h>
+ #include <pulsecore/dbus-shared.h>
+ #include <pulsecore/shared.h>
+@@ -518,8 +520,16 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage
+         pa_log_warn("New audio connection on unknown card %s (fd=%d, codec=%d)", card, fd, codec);
+         pa_assert_se(r = dbus_message_new_error(m, "org.ofono.Error.InvalidArguments", "Unknown card"));
+         return r;
+-    } else
+-        pa_log_debug("New audio connection on card %s (fd=%d, codec=%d)", card, fd, codec);
++    }
++
++    pa_log_debug("New audio connection on card %s (fd=%d, codec=%d)", card, fd, codec);
++
++    /* Do the socket defered setup */
++    if (recv(fd, NULL, 0, 0) < 0) {
++        const char *strerr = strerror(errno);
++        pa_log_warn("Defered setup failed: %d (%s)", errno, strerr);
++        pa_assert_se(r = dbus_message_new_error(m, "org.ofono.Error.InvalidArguments", strerr));
++    }
+     hfac->fd = fd;
+     hfac->codec = codec;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0024-bluetooth-Suspend-sink-source-on-HFP-s-stream-HUP.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0024-bluetooth-Suspend-sink-source-on-HFP-s-stream-HUP.patch
new file mode 100644 (file)
index 0000000..5858d67
--- /dev/null
@@ -0,0 +1,60 @@
+From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= <jprvita@openbossa.org>
+Date: Wed, 27 Mar 2013 01:43:42 -0300
+Subject: bluetooth: Suspend sink/source on HFP's stream HUP
+
+When the Audio Connection is disconnected the sink and source should be
+suspended.
+
+Change-Id: Ifedd5e7fe70ee74e509b82270ce84aba762f2412
+---
+ src/modules/bluetooth/module-bluez5-device.c | 17 ++++++++++++++---
+ 1 file changed, 14 insertions(+), 3 deletions(-)
+
+diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
+index b511b92..ae5ec1d 100644
+--- a/src/modules/bluetooth/module-bluez5-device.c
++++ b/src/modules/bluetooth/module-bluez5-device.c
+@@ -74,6 +74,7 @@ static const char* const valid_modargs[] = {
+ enum {
+     BLUETOOTH_MESSAGE_IO_THREAD_FAILED,
++    BLUETOOTH_MESSAGE_TRANSPORT_STATE_CHANGED,
+     BLUETOOTH_MESSAGE_MAX
+ };
+@@ -1427,6 +1428,12 @@ io_fail:
+         pending_read_bytes = 0;
+         writable = false;
++        if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
++            u->transport->state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
++            pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_TRANSPORT_STATE_CHANGED, u, 0,
++                              NULL, NULL);
++        }
++
+         teardown_stream(u);
+     }
+@@ -1994,15 +2001,19 @@ static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa
+ /* Run from main thread context */
+ static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+-    struct bluetooth_msg *u = BLUETOOTH_MSG(obj);
++    struct bluetooth_msg *b = BLUETOOTH_MSG(obj);
++    struct userdata *u = data;
+     switch (code) {
++        case BLUETOOTH_MESSAGE_TRANSPORT_STATE_CHANGED:
++            handle_transport_state_change(u, u->transport);
++            break;
+         case BLUETOOTH_MESSAGE_IO_THREAD_FAILED:
+-            if (u->card->module->unload_requested)
++            if (b->card->module->unload_requested)
+                 break;
+             pa_log_debug("Switching the profile to off due to IO thread failure.");
+-            pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
++            pa_assert_se(pa_card_set_profile(b->card, "off", false) >= 0);
+             break;
+     }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0025-bluetooth-Implement-transport-release-for-hf_audio_a.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0025-bluetooth-Implement-transport-release-for-hf_audio_a.patch
new file mode 100644 (file)
index 0000000..b557ea5
--- /dev/null
@@ -0,0 +1,27 @@
+From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Date: Fri, 23 Aug 2013 19:19:34 +0300
+Subject: bluetooth: Implement transport release for hf_audio_agent transports
+
+Change-Id: I496b3ab1c2f8e347c41262818ec3b9a35ed7262e
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index 90d1348..a0474df 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -153,6 +153,13 @@ static int hf_audio_agent_transport_acquire(pa_bluetooth_transport *t, bool opti
+ }
+ static void hf_audio_agent_transport_release(pa_bluetooth_transport *t) {
++    hf_audio_agent_data *hfdata = t->userdata;
++    hf_audio_card *hfac = pa_hashmap_get(hfdata->hf_audio_cards, t->path);
++
++    if (hfac) {
++        shutdown(hfac->fd, SHUT_RDWR);
++        hfac->fd = -1;
++    }
+ }
+ static void hf_audio_agent_card_found(hf_audio_agent_data *hfdata, const char *path, DBusMessageIter *props_i) {
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0026-bluetooth-Fixes-HFP-audio-transfer-when-initiator.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0026-bluetooth-Fixes-HFP-audio-transfer-when-initiator.patch
new file mode 100644 (file)
index 0000000..4dd9742
--- /dev/null
@@ -0,0 +1,59 @@
+From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Date: Fri, 23 Aug 2013 19:44:23 +0300
+Subject: bluetooth: Fixes HFP audio transfer when initiator
+
+This makes sure org.ofono.HandsfreeAudioCard.Connect is called regardless
+of the optional flag and also makes sure to update the profile state
+whenever SCO is disconnected.
+
+Change-Id: I4400753333f14a2381eb75d5b62d2ea51d1c7139
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c   |  2 +-
+ src/modules/bluetooth/module-bluez5-device.c | 14 +++++++-------
+ 2 files changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index a0474df..59eafdb 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -126,7 +126,7 @@ static int hf_audio_agent_transport_acquire(pa_bluetooth_transport *t, bool opti
+     hf_audio_agent_data *hfdata = t->userdata;
+     hf_audio_card *hfac = pa_hashmap_get(hfdata->hf_audio_cards, t->path);
+-    if (!optional) {
++    if (hfac->fd < 0) {
+         DBusMessage *m;
+         pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.ofono.HandsfreeAudioCard", "Connect"));
+diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
+index ae5ec1d..39fa5d0 100644
+--- a/src/modules/bluetooth/module-bluez5-device.c
++++ b/src/modules/bluetooth/module-bluez5-device.c
+@@ -768,6 +768,12 @@ static void transport_release(struct userdata *u) {
+     u->transport_acquired = false;
++    if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
++        u->transport->state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
++        pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_TRANSPORT_STATE_CHANGED, u, 0,
++                              NULL, NULL);
++    }
++
+     teardown_stream(u);
+ }
+@@ -1428,13 +1434,7 @@ io_fail:
+         pending_read_bytes = 0;
+         writable = false;
+-        if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
+-            u->transport->state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
+-            pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_TRANSPORT_STATE_CHANGED, u, 0,
+-                              NULL, NULL);
+-        }
+-
+-        teardown_stream(u);
++        transport_release(u);
+     }
+ fail:
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0027-bluetooth-Set-off-profile-as-default-for-newly-creat.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0027-bluetooth-Set-off-profile-as-default-for-newly-creat.patch
new file mode 100644 (file)
index 0000000..cce0497
--- /dev/null
@@ -0,0 +1,37 @@
+From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Date: Mon, 16 Sep 2013 15:10:51 +0300
+Subject: bluetooth: Set 'off' profile as default for newly create cards
+
+This makes sure that pa_card_new doesn't select one profile based on
+priority which may conflict with audio policy.
+
+Change-Id: Ifa5ad111d0c9f58701f93bcfd85b70d1f096efac
+---
+ src/modules/bluetooth/module-bluez4-device.c | 1 +
+ src/modules/bluetooth/module-bluez5-device.c | 1 +
+ 2 files changed, 2 insertions(+)
+
+diff --git a/src/modules/bluetooth/module-bluez4-device.c b/src/modules/bluetooth/module-bluez4-device.c
+index 83e603f..b0b12f8 100644
+--- a/src/modules/bluetooth/module-bluez4-device.c
++++ b/src/modules/bluetooth/module-bluez4-device.c
+@@ -2295,6 +2295,7 @@ static int add_card(struct userdata *u) {
+     d = PA_CARD_PROFILE_DATA(p);
+     *d = PA_BLUEZ4_PROFILE_OFF;
+     pa_hashmap_put(data.profiles, p->name, p);
++    pa_card_new_data_set_profile(&data, "off");
+     if ((default_profile = pa_modargs_get_value(u->modargs, "profile", NULL))) {
+         if (pa_hashmap_get(data.profiles, default_profile))
+diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
+index 39fa5d0..a0776c8 100644
+--- a/src/modules/bluetooth/module-bluez5-device.c
++++ b/src/modules/bluetooth/module-bluez5-device.c
+@@ -1888,6 +1888,7 @@ static int add_card(struct userdata *u) {
+     p = PA_CARD_PROFILE_DATA(cp);
+     *p = PA_BLUETOOTH_PROFILE_OFF;
+     pa_hashmap_put(data.profiles, cp->name, cp);
++    pa_card_new_data_set_profile(&data, "off");
+     u->card = pa_card_new(u->core, &data);
+     pa_card_new_data_done(&data);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0028-fix-ofono-and-pulseaudio-starting-order-assert.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0028-fix-ofono-and-pulseaudio-starting-order-assert.patch
new file mode 100644 (file)
index 0000000..db44a2e
--- /dev/null
@@ -0,0 +1,27 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Thu, 2 Jan 2014 15:37:02 +0200
+Subject: fix ofono and pulseaudio starting order assert
+
+Change-Id: I743c6e1fb5c65cc0702f073d6c2beb4e6868d7bb
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index 59eafdb..7f93c6b 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -403,10 +403,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *da
+             if (old_owner && *old_owner) {
+                 pa_log_debug("oFono disappeared");
+-                if (hfdata->hf_audio_cards) {
+-                    pa_hashmap_free(hfdata->hf_audio_cards);
+-                    hfdata->hf_audio_cards = NULL;
+-                }
++                if (hfdata->hf_audio_cards)
++                    pa_hashmap_remove_all(hfdata->hf_audio_cards);
+                 if(hfdata->ofono_bus_id) {
+                     pa_xfree(hfdata->ofono_bus_id);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0029-hfp-do-safe-strcmp-in-dbus-handler.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0029-hfp-do-safe-strcmp-in-dbus-handler.patch
new file mode 100644 (file)
index 0000000..8402ffa
--- /dev/null
@@ -0,0 +1,23 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Mon, 30 Dec 2013 18:03:18 +0200
+Subject: hfp do safe strcmp in dbus handler
+
+Change-Id: I4ba64d22b2b807530263b5f274cd89f208c675ac
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/bluetooth/hfaudioagent-ofono.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/modules/bluetooth/hfaudioagent-ofono.c b/src/modules/bluetooth/hfaudioagent-ofono.c
+index 7f93c6b..2ac8a82 100644
+--- a/src/modules/bluetooth/hfaudioagent-ofono.c
++++ b/src/modules/bluetooth/hfaudioagent-ofono.c
+@@ -381,7 +381,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *da
+     pa_assert(hfdata);
+     sender = dbus_message_get_sender(m);
+-    if (!pa_safe_streq(hfdata->ofono_bus_id, sender) && !pa_streq("org.freedesktop.DBus", sender))
++    if (!pa_safe_streq(hfdata->ofono_bus_id, sender) && !pa_safe_streq("org.freedesktop.DBus", sender))
+         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+     dbus_error_init(&err);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0030-add-parameter-to-define-key-used-in-stream-restore.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0030-add-parameter-to-define-key-used-in-stream-restore.patch
new file mode 100644 (file)
index 0000000..0773d40
--- /dev/null
@@ -0,0 +1,258 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Thu, 5 Sep 2013 12:21:19 +0300
+Subject: add parameter to define key used in stream restore.
+
+It is possible now to use preferred_stream_group
+command line parameter when loading module stream
+restore. This key will be searched from the stream's
+proplist and if found it is used as key when
+restoring the volumes and other values. There's also
+special value media.role.within.appication.name you
+can use to enable restoring stream roles within
+application. So different streams with different
+roles within application will save their volumes.
+If the preferred stream group parameter is left out
+module stream restore will fallback to old default
+functionality.
+
+Change-Id: I636f47b43476f3d4cd6c14244eafcd050683bb69
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/module-stream-restore.c | 34 +++++++++++++---------
+ src/pulsecore/proplist-util.c       | 58 +++++++++++++++++++++++++++++++++++++
+ src/pulsecore/proplist-util.h       |  1 +
+ 3 files changed, 80 insertions(+), 13 deletions(-)
+
+diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c
+index 9689e79..68968c9 100644
+--- a/src/modules/module-stream-restore.c
++++ b/src/modules/module-stream-restore.c
+@@ -70,7 +70,8 @@ PA_MODULE_USAGE(
+         "restore_muted=<Save/restore muted states?> "
+         "on_hotplug=<When new device becomes available, recheck streams?> "
+         "on_rescue=<When device becomes unavailable, recheck streams?> "
+-        "fallback_table=<filename>");
++        "fallback_table=<filename>"
++        "preferred_stream_group=<prefer certain stream group in restore?> ");
+ #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
+ #define IDENTIFICATION_PROPERTY "module-stream-restore.id"
+@@ -87,6 +88,7 @@ static const char* const valid_modargs[] = {
+     "on_hotplug",
+     "on_rescue",
+     "fallback_table",
++    "preferred_stream_group",
+     NULL
+ };
+@@ -112,6 +114,7 @@ struct userdata {
+     bool restore_muted:1;
+     bool on_hotplug:1;
+     bool on_rescue:1;
++    char *preferred_stream_group;
+     pa_native_protocol *protocol;
+     pa_idxset *subscribed;
+@@ -1277,7 +1280,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
+         if (!(sink_input = pa_idxset_get_by_index(c->sink_inputs, idx)))
+             return;
+-        if (!(name = pa_proplist_get_stream_group(sink_input->proplist, "sink-input", IDENTIFICATION_PROPERTY)))
++        if (!(name = pa_proplist_get_stream_group_extended(sink_input->proplist, "sink-input", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+             return;
+         if ((old = entry_read(u, name))) {
+@@ -1327,7 +1330,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
+         if (!(source_output = pa_idxset_get_by_index(c->source_outputs, idx)))
+             return;
+-        if (!(name = pa_proplist_get_stream_group(source_output->proplist, "source-output", IDENTIFICATION_PROPERTY)))
++        if (!(name = pa_proplist_get_stream_group_extended(source_output->proplist, "source-output", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+             return;
+         if ((old = entry_read(u, name))) {
+@@ -1420,7 +1423,7 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
+     pa_assert(u);
+     pa_assert(u->restore_device);
+-    if (!(name = pa_proplist_get_stream_group(new_data->proplist, "sink-input", IDENTIFICATION_PROPERTY)))
++    if (!(name = pa_proplist_get_stream_group_extended(new_data->proplist, "sink-input", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+         return PA_HOOK_OK;
+     if (new_data->sink)
+@@ -1462,7 +1465,7 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu
+     pa_assert(u);
+     pa_assert(u->restore_volume || u->restore_muted);
+-    if (!(name = pa_proplist_get_stream_group(new_data->proplist, "sink-input", IDENTIFICATION_PROPERTY)))
++    if (!(name = pa_proplist_get_stream_group_extended(new_data->proplist, "sink-input", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+         return PA_HOOK_OK;
+     if ((e = entry_read(u, name))) {
+@@ -1516,7 +1519,7 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou
+     if (new_data->direct_on_input)
+         return PA_HOOK_OK;
+-    if (!(name = pa_proplist_get_stream_group(new_data->proplist, "source-output", IDENTIFICATION_PROPERTY)))
++    if (!(name = pa_proplist_get_stream_group_extended(new_data->proplist, "source-output", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+         return PA_HOOK_OK;
+     if (new_data->source)
+@@ -1559,7 +1562,7 @@ static pa_hook_result_t source_output_fixate_hook_callback(pa_core *c, pa_source
+     pa_assert(u);
+     pa_assert(u->restore_volume || u->restore_muted);
+-    if (!(name = pa_proplist_get_stream_group(new_data->proplist, "source-output", IDENTIFICATION_PROPERTY)))
++    if (!(name = pa_proplist_get_stream_group_extended(new_data->proplist, "source-output", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+         return PA_HOOK_OK;
+     if ((e = entry_read(u, name))) {
+@@ -1631,7 +1634,7 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct
+         if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si)))
+             continue;
+-        if (!(name = pa_proplist_get_stream_group(si->proplist, "sink-input", IDENTIFICATION_PROPERTY)))
++        if (!(name = pa_proplist_get_stream_group_extended(si->proplist, "sink-input", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+             continue;
+         if ((e = entry_read(u, name))) {
+@@ -1679,7 +1682,7 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source,
+         if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so)))
+             continue;
+-        if (!(name = pa_proplist_get_stream_group(so->proplist, "source-output", IDENTIFICATION_PROPERTY)))
++        if (!(name = pa_proplist_get_stream_group_extended(so->proplist, "source-output", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+             continue;
+         if ((e = entry_read(u, name))) {
+@@ -1715,7 +1718,7 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str
+         if (!si->sink)
+             continue;
+-        if (!(name = pa_proplist_get_stream_group(si->proplist, "sink-input", IDENTIFICATION_PROPERTY)))
++        if (!(name = pa_proplist_get_stream_group_extended(si->proplist, "sink-input", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+             continue;
+         if ((e = entry_read(u, name))) {
+@@ -1761,7 +1764,7 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc
+         if (!so->source)
+             continue;
+-        if (!(name = pa_proplist_get_stream_group(so->proplist, "source-output", IDENTIFICATION_PROPERTY)))
++        if (!(name = pa_proplist_get_stream_group_extended(so->proplist, "source-output", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+             continue;
+         if ((e = entry_read(u, name))) {
+@@ -1880,7 +1883,7 @@ static void entry_apply(struct userdata *u, const char *name, struct entry *e) {
+         char *n;
+         pa_sink *s;
+-        if (!(n = pa_proplist_get_stream_group(si->proplist, "sink-input", IDENTIFICATION_PROPERTY)))
++        if (!(n = pa_proplist_get_stream_group_extended(si->proplist, "sink-input", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+             continue;
+         if (!pa_streq(name, n)) {
+@@ -1928,7 +1931,7 @@ static void entry_apply(struct userdata *u, const char *name, struct entry *e) {
+         char *n;
+         pa_source *s;
+-        if (!(n = pa_proplist_get_stream_group(so->proplist, "source-output", IDENTIFICATION_PROPERTY)))
++        if (!(n = pa_proplist_get_stream_group_extended(so->proplist, "source-output", IDENTIFICATION_PROPERTY, u->preferred_stream_group)))
+             continue;
+         if (!pa_streq(name, n)) {
+@@ -2411,6 +2414,8 @@ int pa__init(pa_module*m) {
+     u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u);
++    u->preferred_stream_group = pa_xstrdup(pa_modargs_get_value(ma, "preferred_stream_group", NULL));
++
+     if (restore_device) {
+         /* A little bit earlier than module-intended-roles ... */
+         u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_new_hook_callback, u);
+@@ -2554,5 +2559,8 @@ void pa__done(pa_module*m) {
+     if (u->subscribed)
+         pa_idxset_free(u->subscribed, NULL);
++    if (u->preferred_stream_group)
++        pa_xfree(u->preferred_stream_group);
++
+     pa_xfree(u);
+ }
+diff --git a/src/pulsecore/proplist-util.c b/src/pulsecore/proplist-util.c
+index 473290f..0a8dbca 100644
+--- a/src/pulsecore/proplist-util.c
++++ b/src/pulsecore/proplist-util.c
+@@ -274,3 +274,61 @@ char *pa_proplist_get_stream_group(pa_proplist *p, const char *prefix, const cha
+     return t;
+ }
++
++char *pa_proplist_get_stream_group_extended(pa_proplist *p, const char *prefix, const char *cache, const char *preferred_stream_group) {
++    const char *r = NULL;
++    const char *q = NULL;
++    char *t;
++
++    if (!p)
++        return NULL;
++
++    if (cache && (r = pa_proplist_gets(p, cache)))
++        return pa_xstrdup(r);
++
++    if (!prefix)
++        prefix = "stream";
++
++    /* try first to get the preferred stream group, then fallback to hard coded order */
++    if (preferred_stream_group) {
++        if (!strcmp(preferred_stream_group, "media.role.within.application.name")) {
++          if ((r = pa_proplist_gets(p, PA_PROP_APPLICATION_NAME)) &&
++              (q = pa_proplist_gets(p, PA_PROP_MEDIA_ROLE))) {
++                    t = pa_sprintf_malloc("%s-by-media-role-within-application-name:%s-%s", prefix, q, r);
++          } else {
++              /* make r NULL to be able to fallback to "standard" stream restore code */
++                r = NULL;
++          }
++      }
++      else if ((r = pa_proplist_gets(p, preferred_stream_group))) {
++            if (!strcmp(preferred_stream_group, PA_PROP_MEDIA_ROLE))
++                t = pa_sprintf_malloc("%s-by-media-role:%s", prefix, r);
++          else if (!strcmp(preferred_stream_group, PA_PROP_APPLICATION_ID))
++                t = pa_sprintf_malloc("%s-by-application-id:%s", prefix, r);
++            else if (!strcmp(preferred_stream_group, PA_PROP_APPLICATION_NAME))
++                t = pa_sprintf_malloc("%s-by-application-name:%s", prefix, r);
++            else if (!strcmp(preferred_stream_group, PA_PROP_MEDIA_NAME))
++              t = pa_sprintf_malloc("%s-by-media-name:%s", prefix, r);
++            else
++                t = pa_sprintf_malloc("%s-fallback:%s", prefix, r);
++        }
++    }
++
++    if (!r) {
++        if ((r = pa_proplist_gets(p, PA_PROP_MEDIA_ROLE)))
++            t = pa_sprintf_malloc("%s-by-media-role:%s", prefix, r);
++        else if ((r = pa_proplist_gets(p, PA_PROP_APPLICATION_ID)))
++            t = pa_sprintf_malloc("%s-by-application-id:%s", prefix, r);
++        else if ((r = pa_proplist_gets(p, PA_PROP_APPLICATION_NAME)))
++            t = pa_sprintf_malloc("%s-by-application-name:%s", prefix, r);
++        else if ((r = pa_proplist_gets(p, PA_PROP_MEDIA_NAME)))
++            t = pa_sprintf_malloc("%s-by-media-name:%s", prefix, r);
++        else
++            t = pa_sprintf_malloc("%s-fallback:%s", prefix, r);
++    }
++
++    if (cache)
++        pa_proplist_sets(p, cache, t);
++
++    return t;
++}
+diff --git a/src/pulsecore/proplist-util.h b/src/pulsecore/proplist-util.h
+index 3d08776..47bdb57 100644
+--- a/src/pulsecore/proplist-util.h
++++ b/src/pulsecore/proplist-util.h
+@@ -26,5 +26,6 @@
+ void pa_init_proplist(pa_proplist *p);
+ char *pa_proplist_get_stream_group(pa_proplist *pl, const char *prefix, const char *cache);
++char *pa_proplist_get_stream_group_extended(pa_proplist *p, const char *prefix, const char *cache, const char *preferred_stream_group);
+ #endif
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0031-increase-alsa-rewind-safeguard.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0031-increase-alsa-rewind-safeguard.patch
new file mode 100644 (file)
index 0000000..eac1811
--- /dev/null
@@ -0,0 +1,39 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Mon, 28 Apr 2014 11:26:15 +0300
+Subject: increase alsa rewind safeguard
+
+In some devices alsa drivers behaves badly
+if pulseaudio rewind safeguard is too small.
+This is not fixing the driver issues, but is
+a workaround to give time (1.33ms->5ms) for
+user space processing so that alsa is not
+getting into this weird state. This could be
+also helped by running pulseaudio in realtime.
+This is of course increasing the volume setting
+latency, but should leave it still quite
+responsive.
+
+Change-Id: Iecdf879bf8ba58e991808d2dc382def05de36ec9
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/alsa/alsa-sink.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
+index ccf1137..5a41cf6 100644
+--- a/src/modules/alsa/alsa-sink.c
++++ b/src/modules/alsa/alsa-sink.c
+@@ -87,8 +87,13 @@
+ #define VOLUME_ACCURACY (PA_VOLUME_NORM/100)  /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */
++#if 0
+ #define DEFAULT_REWIND_SAFEGUARD_BYTES (256U) /* 1.33ms @48kHz, we'll never rewind less than this */
+ #define DEFAULT_REWIND_SAFEGUARD_USEC (1330) /* 1.33ms, depending on channels/rate/sample we may rewind more than 256 above */
++#endif
++
++#define DEFAULT_REWIND_SAFEGUARD_BYTES (1024U) /* increase safeguard 4x */
++#define DEFAULT_REWIND_SAFEGUARD_USEC (5000) /* increase safeguard ~4x */
+ struct userdata {
+     pa_core *core;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0032-fix-for-profile-change-prototype-in-bluez5-patch.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0032-fix-for-profile-change-prototype-in-bluez5-patch.patch
new file mode 100644 (file)
index 0000000..646694a
--- /dev/null
@@ -0,0 +1,23 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Tue, 11 Mar 2014 12:23:09 +0200
+Subject: fix for profile change prototype in bluez5 patch
+
+Change-Id: I2361c4eca82e6ac4a8f94e9f9c97f09cb6648049
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/bluetooth/module-bluez5-device.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
+index a0776c8..790dcf1 100644
+--- a/src/modules/bluetooth/module-bluez5-device.c
++++ b/src/modules/bluetooth/module-bluez5-device.c
+@@ -2014,7 +2014,7 @@ static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t o
+                 break;
+             pa_log_debug("Switching the profile to off due to IO thread failure.");
+-            pa_assert_se(pa_card_set_profile(b->card, "off", false) >= 0);
++            pa_assert_se(pa_card_set_profile(b->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
+             break;
+     }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0033-changes-to-pa-simple-api-samsung.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0033-changes-to-pa-simple-api-samsung.patch
new file mode 100644 (file)
index 0000000..e041892
--- /dev/null
@@ -0,0 +1,438 @@
+From: "vivian,zhang" <vivian.zhang@intel.com>
+Date: Tue, 18 Jun 2013 16:10:15 +0800
+Subject: changes to pa simple api - samsung
+
+Change-Id: I997c02217a8dc14524480164aa0baeea901c7b4e
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/Makefile.am    |   4 +-
+ src/map-file       |   6 ++
+ src/pulse/simple.c | 288 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ src/pulse/simple.h |  28 ++++++
+ 4 files changed, 324 insertions(+), 2 deletions(-)
+
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 2edce5f..5ec8609 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -824,7 +824,7 @@ libpulse_la_SOURCES = \
+               pulse/volume.c pulse/volume.h \
+               pulse/xmalloc.c pulse/xmalloc.h
+-libpulse_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS)
++libpulse_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(LIBJSON_CFLAGS)
+ libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBJSON_LIBS) libpulsecommon-@PA_MAJORMINOR@.la
+ libpulse_la_LDFLAGS = $(AM_LDFLAGS) $(VERSIONING_LDFLAGS) -version-info $(LIBPULSE_VERSION_INFO)
+@@ -834,7 +834,7 @@ libpulse_la_LIBADD += $(DBUS_LIBS)
+ endif
+ libpulse_simple_la_SOURCES = pulse/simple.c pulse/simple.h
+-libpulse_simple_la_CFLAGS = $(AM_CFLAGS)
++libpulse_simple_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+ libpulse_simple_la_LIBADD = $(AM_LIBADD) libpulse.la libpulsecommon-@PA_MAJORMINOR@.la
+ libpulse_simple_la_LDFLAGS = $(AM_LDFLAGS) $(VERSIONING_LDFLAGS) -version-info $(LIBPULSE_SIMPLE_VERSION_INFO)
+diff --git a/src/map-file b/src/map-file
+index 7dbcd00..95364ae 100644
+--- a/src/map-file
++++ b/src/map-file
+@@ -282,11 +282,17 @@ pa_signal_new;
+ pa_signal_set_destroy;
+ pa_simple_drain;
+ pa_simple_flush;
++pa_simple_mute;
+ pa_simple_free;
+ pa_simple_get_latency;
+ pa_simple_new;
++pa_simple_new_proplist;
+ pa_simple_read;
+ pa_simple_write;
++pa_simple_set_volume;
++pa_simple_get_stream_index;
++pa_simple_cork;
++pa_simple_is_corked;
+ pa_stream_begin_write;
+ pa_stream_cancel_write;
+ pa_stream_connect_playback;
+diff --git a/src/pulse/simple.c b/src/pulse/simple.c
+index 1891131..9109ffa 100644
+--- a/src/pulse/simple.c
++++ b/src/pulse/simple.c
+@@ -32,10 +32,12 @@
+ #include <pulse/thread-mainloop.h>
+ #include <pulse/xmalloc.h>
++#include <pulsecore/native-common.h>
+ #include <pulsecore/log.h>
+ #include <pulsecore/macro.h>
+ #include "simple.h"
++#include "internal.h"
+ struct pa_simple {
+     pa_threaded_mainloop *mainloop;
+@@ -102,6 +104,24 @@ static void context_state_cb(pa_context *c, void *userdata) {
+     }
+ }
++static void stream_success_context_cb(pa_stream *s, int success, void *userdata) {
++      pa_simple *p = userdata;
++      pa_assert(s);
++      pa_assert(p);
++
++    p->operation_success = success;
++    pa_threaded_mainloop_signal(p->mainloop, 0);
++}
++
++static void success_context_cb(pa_context *c, int success, void *userdata) {
++      pa_simple *p = userdata;
++      pa_assert(c);
++      pa_assert(p);
++
++    p->operation_success = success;
++    pa_threaded_mainloop_signal(p->mainloop, 0);
++}
++
+ static void stream_state_cb(pa_stream *s, void * userdata) {
+     pa_simple *p = userdata;
+     pa_assert(s);
+@@ -251,6 +271,122 @@ fail:
+     return NULL;
+ }
++pa_simple* pa_simple_new_proplist(
++        const char *server,
++        const char *name,
++        pa_stream_direction_t dir,
++        const char *dev,
++        const char *stream_name,
++        const pa_sample_spec *ss,
++        const pa_channel_map *map,
++        const pa_buffer_attr *attr,
++        pa_proplist *proplist,
++        int *rerror) {
++
++    pa_simple *p;
++    int error = PA_ERR_INTERNAL, r;
++
++    CHECK_VALIDITY_RETURN_ANY(rerror, !server || *server, PA_ERR_INVALID, NULL);
++    CHECK_VALIDITY_RETURN_ANY(rerror, dir == PA_STREAM_PLAYBACK || dir == PA_STREAM_RECORD, PA_ERR_INVALID, NULL);
++    CHECK_VALIDITY_RETURN_ANY(rerror, !dev || *dev, PA_ERR_INVALID, NULL);
++    CHECK_VALIDITY_RETURN_ANY(rerror, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID, NULL);
++    CHECK_VALIDITY_RETURN_ANY(rerror, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID, NULL)
++
++    p = pa_xnew0(pa_simple, 1);
++    p->direction = dir;
++
++    if (!(p->mainloop = pa_threaded_mainloop_new()))
++        goto fail;
++
++    if (!(p->context = pa_context_new(pa_threaded_mainloop_get_api(p->mainloop), name)))
++        goto fail;
++
++    pa_context_set_state_callback(p->context, context_state_cb, p);
++
++    if (pa_context_connect(p->context, server, 0, NULL) < 0) {
++        error = pa_context_errno(p->context);
++        goto fail;
++    }
++
++    pa_threaded_mainloop_lock(p->mainloop);
++
++    if (pa_threaded_mainloop_start(p->mainloop) < 0)
++        goto unlock_and_fail;
++
++    for (;;) {
++        pa_context_state_t state;
++
++        state = pa_context_get_state(p->context);
++
++        if (state == PA_CONTEXT_READY)
++            break;
++
++        if (!PA_CONTEXT_IS_GOOD(state)) {
++            error = pa_context_errno(p->context);
++            goto unlock_and_fail;
++        }
++
++        /* Wait until the context is ready */
++        pa_threaded_mainloop_wait(p->mainloop);
++    }
++
++    if (!(p->stream = pa_stream_new_with_proplist(p->context, stream_name, ss, map, proplist))) {
++        error = pa_context_errno(p->context);
++        goto unlock_and_fail;
++    }
++
++    pa_stream_set_state_callback(p->stream, stream_state_cb, p);
++    pa_stream_set_read_callback(p->stream, stream_request_cb, p);
++    pa_stream_set_write_callback(p->stream, stream_request_cb, p);
++    pa_stream_set_latency_update_callback(p->stream, stream_latency_update_cb, p);
++
++    if (dir == PA_STREAM_PLAYBACK)
++        r = pa_stream_connect_playback(p->stream, dev, attr,
++                                       PA_STREAM_INTERPOLATE_TIMING
++                                       |PA_STREAM_ADJUST_LATENCY
++                                       |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
++    else
++        r = pa_stream_connect_record(p->stream, dev, attr,
++                                     PA_STREAM_INTERPOLATE_TIMING
++                                     |PA_STREAM_ADJUST_LATENCY
++                                     |PA_STREAM_AUTO_TIMING_UPDATE
++                                     |PA_STREAM_START_CORKED);
++
++    if (r < 0) {
++        error = pa_context_errno(p->context);
++        goto unlock_and_fail;
++    }
++
++    for (;;) {
++        pa_stream_state_t state;
++
++        state = pa_stream_get_state(p->stream);
++
++        if (state == PA_STREAM_READY)
++            break;
++
++        if (!PA_STREAM_IS_GOOD(state)) {
++            error = pa_context_errno(p->context);
++            goto unlock_and_fail;
++        }
++
++        /* Wait until the stream is ready */
++        pa_threaded_mainloop_wait(p->mainloop);
++    }
++
++    pa_threaded_mainloop_unlock(p->mainloop);
++
++    return p;
++
++unlock_and_fail:
++    pa_threaded_mainloop_unlock(p->mainloop);
++
++fail:
++    if (rerror)
++        *rerror = error;
++    pa_simple_free(p);
++    return NULL;
++}
+ void pa_simple_free(pa_simple *s) {
+     pa_assert(s);
+@@ -452,6 +588,111 @@ unlock_and_fail:
+     return -1;
+ }
++int pa_simple_mute(pa_simple *p, int mute, int *rerror) {
++    pa_operation *o = NULL;
++    uint32_t idx;
++
++    pa_assert(p);
++
++    CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1);
++
++    pa_threaded_mainloop_lock(p->mainloop);
++    CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
++
++    CHECK_SUCCESS_GOTO(p, rerror, ((idx = pa_stream_get_index (p->stream)) != PA_INVALID_INDEX), unlock_and_fail);
++
++
++    o = pa_context_set_sink_input_mute (p->context, idx, mute, success_context_cb, p);
++    CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail);
++
++    p->operation_success = 0;
++    while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
++        pa_threaded_mainloop_wait(p->mainloop);
++        CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
++    }
++    CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail);
++
++    pa_operation_unref(o);
++    pa_threaded_mainloop_unlock(p->mainloop);
++
++    return 0;
++
++unlock_and_fail:
++
++    if (o) {
++        pa_operation_cancel(o);
++        pa_operation_unref(o);
++    }
++
++    pa_threaded_mainloop_unlock(p->mainloop);
++    return -1;
++}
++
++int pa_simple_get_stream_index(pa_simple *p, unsigned int *idx, int *rerror) {
++    pa_assert(p);
++    CHECK_VALIDITY_RETURN_ANY(rerror, idx != NULL, PA_ERR_INVALID, -1);
++
++    pa_threaded_mainloop_lock(p->mainloop);
++
++    CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
++
++      *idx = pa_stream_get_index(p->stream);
++
++    pa_threaded_mainloop_unlock(p->mainloop);
++    return 0;
++
++unlock_and_fail:
++    pa_threaded_mainloop_unlock(p->mainloop);
++    return -1;
++}
++
++int pa_simple_set_volume(pa_simple *p, int volume, int *rerror) {
++    pa_operation *o = NULL;
++    pa_stream *s = NULL;
++    uint32_t idx;
++    pa_cvolume cv;
++
++
++    pa_assert(p);
++
++    CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1);
++    CHECK_VALIDITY_RETURN_ANY(rerror, volume >= 0, PA_ERR_INVALID, -1);
++    CHECK_VALIDITY_RETURN_ANY(rerror, volume <= 65535, PA_ERR_INVALID, -1);
++
++    pa_threaded_mainloop_lock(p->mainloop);
++    CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
++
++    CHECK_SUCCESS_GOTO(p, rerror, ((idx = pa_stream_get_index (p->stream)) != PA_INVALID_INDEX), unlock_and_fail);
++
++    s = p->stream;
++    pa_assert(s);
++    pa_cvolume_set(&cv, s->sample_spec.channels, volume);
++
++    o = pa_context_set_sink_input_volume (p->context, idx, &cv, success_context_cb, p);
++    CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail);
++
++    p->operation_success = 0;
++    while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
++        pa_threaded_mainloop_wait(p->mainloop);
++        CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
++    }
++    CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail);
++
++    pa_operation_unref(o);
++    pa_threaded_mainloop_unlock(p->mainloop);
++
++    return 0;
++
++unlock_and_fail:
++
++    if (o) {
++        pa_operation_cancel(o);
++        pa_operation_unref(o);
++    }
++
++    pa_threaded_mainloop_unlock(p->mainloop);
++    return -1;
++}
+ pa_usec_t pa_simple_get_latency(pa_simple *p, int *rerror) {
+     pa_usec_t t;
+     int negative;
+@@ -481,3 +722,50 @@ unlock_and_fail:
+     pa_threaded_mainloop_unlock(p->mainloop);
+     return (pa_usec_t) -1;
+ }
++
++int pa_simple_cork(pa_simple *p, int cork, int *rerror) {
++    pa_operation *o = NULL;
++
++    pa_assert(p);
++
++    pa_threaded_mainloop_lock(p->mainloop);
++    CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
++
++    o = pa_stream_cork(p->stream, cork, stream_success_context_cb, p);
++    CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail);
++
++    p->operation_success = 0;
++    while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
++        pa_threaded_mainloop_wait(p->mainloop);
++        CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
++    }
++    CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail);
++
++    pa_operation_unref(o);
++    pa_threaded_mainloop_unlock(p->mainloop);
++
++    return 0;
++
++unlock_and_fail:
++
++    if (o) {
++        pa_operation_cancel(o);
++        pa_operation_unref(o);
++    }
++
++    pa_threaded_mainloop_unlock(p->mainloop);
++    return -1;
++}
++
++int pa_simple_is_corked(pa_simple *p) {
++      int is_cork;
++    pa_assert(p);
++
++    pa_threaded_mainloop_lock(p->mainloop);
++
++    is_cork = pa_stream_is_corked(p->stream);
++
++    pa_threaded_mainloop_unlock(p->mainloop);
++
++    return is_cork;
++}
+diff --git a/src/pulse/simple.h b/src/pulse/simple.h
+index 54003ff..4f4a988 100644
+--- a/src/pulse/simple.h
++++ b/src/pulse/simple.h
+@@ -31,6 +31,7 @@
+ #include <pulse/cdecl.h>
+ #include <pulse/version.h>
++#include <pulse/proplist.h>
+ /** \page simple Simple API
+  *
+  * \section overv_sec Overview
+@@ -129,6 +130,19 @@ pa_simple* pa_simple_new(
+     int *error                          /**< A pointer where the error code is stored when the routine returns NULL. It is OK to pass NULL here. */
+     );
++/** Create a new connection to the server with proplist */
++pa_simple* pa_simple_new_proplist(
++    const char *server,                 /**< Server name, or NULL for default */
++    const char *name,                   /**< A descriptive name for this client (application name, ...) */
++    pa_stream_direction_t dir,          /**< Open this stream for recording or playback? */
++    const char *dev,                    /**< Sink (resp. source) name, or NULL for default */
++    const char *stream_name,            /**< A descriptive name for this client (application name, song title, ...) */
++    const pa_sample_spec *ss,           /**< The sample type to use */
++    const pa_channel_map *map,          /**< The channel map to use, or NULL for default */
++    const pa_buffer_attr *attr,         /**< Buffering attributes, or NULL for default */
++    pa_proplist *proplist,    /**< Properties, or NULL for default */
++    int *error                          /**< A pointer where the error code is stored when the routine returns NULL. It is OK to pass NULL here. */
++    );
+ /** Close and free the connection to the server. The connection object becomes invalid when this is called. */
+ void pa_simple_free(pa_simple *s);
+@@ -155,6 +169,20 @@ pa_usec_t pa_simple_get_latency(pa_simple *s, int *error);
+ /** Flush the playback or record buffer. This discards any audio in the buffer. */
+ int pa_simple_flush(pa_simple *s, int *error);
++/** Mute the playback stream */
++int pa_simple_mute(pa_simple *p, int mute, int *rerror);
++
++/** Volume control the playback stream */
++int pa_simple_set_volume(pa_simple *p, int volume, int *rerror);
++
++/** Get stream index */
++int pa_simple_get_stream_index(pa_simple *p, unsigned int *idx, int *rerror);
++
++/** Cork on=1/off=0 stream */
++int pa_simple_cork(pa_simple *p, int cork, int *rerror);
++
++/** Check whether stream is corked or not */
++int pa_simple_is_corked(pa_simple *p);
+ PA_C_DECL_END
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0034-add-support-for-samsung-power-management-samsung.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0034-add-support-for-samsung-power-management-samsung.patch
new file mode 100644 (file)
index 0000000..5d2380b
--- /dev/null
@@ -0,0 +1,301 @@
+From: "vivian,zhang" <vivian.zhang@intel.com>
+Date: Tue, 18 Jun 2013 16:11:16 +0800
+Subject: add support for samsung power management - samsung
+
+Change-Id: Id76a3971e36c08773848fdf1ac1cc9a9200d7330
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ configure.ac                         |  12 +++
+ src/Makefile.am                      |   3 +
+ src/modules/module-suspend-on-idle.c | 179 ++++++++++++++++++++++++++++++++++-
+ 3 files changed, 192 insertions(+), 2 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index a35918c..9a79b36 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -673,6 +673,18 @@ AS_IF([test "x$enable_samplerate" = "xyes" && test "x$HAVE_LIBSAMPLERATE" = "x0"
+ AM_CONDITIONAL([HAVE_LIBSAMPLERATE], [test "x$HAVE_LIBSAMPLERATE" = x1])
+ AS_IF([test "x$HAVE_LIBSAMPLERATE" = "x1"], AC_DEFINE([HAVE_LIBSAMPLERATE], 1, [Have libsamplerate?]))
++#### samsung PM API support ####
++
++AC_ARG_ENABLE(pmlock, AC_HELP_STRING([--enable-pmlock], [using Samsung power management api]),
++[
++ case "${enableval}" in
++     yes) USE_PM_LOCK=yes ;;
++     no)  USE_PM_LOCK=no ;;
++     *)   AC_MSG_ERROR(bad value ${enableval} for --enable-pmlock) ;;
++ esac
++ ],[USE_PM_LOCK=no])
++AM_CONDITIONAL(USE_PM_LOCK, test "x$USE_PM_LOCK" = "xyes")
++
+ #### Database support ####
+ AC_ARG_WITH([database],
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 5ec8609..12bb73e 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -1940,6 +1940,9 @@ module_suspend_on_idle_la_SOURCES = modules/module-suspend-on-idle.c
+ module_suspend_on_idle_la_LDFLAGS = $(MODULE_LDFLAGS)
+ module_suspend_on_idle_la_LIBADD = $(MODULE_LIBADD)
+ module_suspend_on_idle_la_CFLAGS = $(AM_CFLAGS)
++if USE_PM_LOCK
++module_suspend_on_idle_la_CFLAGS += -DUSE_PM_LOCK
++endif
+ # echo-cancel module
+ module_echo_cancel_la_SOURCES = \
+diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c
+index 15cbf95..8350917 100644
+--- a/src/modules/module-suspend-on-idle.c
++++ b/src/modules/module-suspend-on-idle.c
+@@ -34,7 +34,53 @@
+ #include <pulsecore/modargs.h>
+ #include <pulsecore/log.h>
++#include <pulsecore/namereg.h>
+ #include "module-suspend-on-idle-symdef.h"
++//move to configure.ac
++//#define USE_PM_LOCK /* Enable as default */
++#ifdef USE_PM_LOCK
++
++#include <sys/types.h>
++#include <sys/socket.h>
++#include <sys/un.h>
++#include <unistd.h>
++#include <stdio.h>
++#include <errno.h>
++#include <linux/limits.h>
++
++#define SOCK_PATH                     "/tmp/pm_sock"
++#define SHIFT_UNLOCK                  4
++#define SHIFT_UNLOCK_PARAMETER                12
++#define SHIFT_CHANGE_STATE            8
++#define SHIFT_HOLD_KEY_BLOCK          16
++#define SHIFT_CHANGE_TIMEOUT          20
++#define TIMEOUT_RESET_BIT             0x80
++
++#define LCD_NORMAL      0x1   /**< NORMAL state */
++#define LCD_DIM         0x2  /**< LCD dimming state */
++#define LCD_OFF         0x4  /**< LCD off state */
++#define SUSPEND         0x8  /**< Sleep state */
++#define POWER_OFF       0x16  /**< Sleep state */
++#define SETALL (LCD_DIM | LCD_OFF | LCD_NORMAL) /*< select all state - not supported yet */
++
++/* parameters for pm_lock_state() */
++#define STAY_CUR_STATE  0x0
++#define GOTO_STATE_NOW  0x1
++#define HOLD_KEY_BLOCK  0x2
++
++/* paramters for pm_unlcok_state() - details are described at 162 line */
++#define PM_SLEEP_MARGIN 0x0     /**< keep guard time for unlock */
++#define PM_RESET_TIMER  0x1     /**< reset timer for unlock */
++#define PM_KEEP_TIMER   0x2     /**< keep timer for unlock */
++
++struct pwr_msg {
++      pid_t pid;
++      unsigned int cond;
++      unsigned int timeout;
++      unsigned int timeout2;
++};
++
++#endif /* USE_PM_LOCK */
+ PA_MODULE_AUTHOR("Lennart Poettering");
+ PA_MODULE_DESCRIPTION("When a sink/source is idle for too long, suspend it");
+@@ -47,6 +93,13 @@ static const char* const valid_modargs[] = {
+     NULL,
+ };
++#ifdef USE_PM_LOCK
++#define PM_TYPE_SINK  0x01
++#define PM_TYPE_SOURCE        0x02
++
++#define UPDATE_PM_LOCK(current,type)  (current |= type)
++#define UPDATE_PM_UNLOCK(current,type)        (current &= ~type)
++#endif /* USE_PM_LOCK */
+ struct userdata {
+     pa_core *core;
+     pa_usec_t timeout;
+@@ -70,6 +123,9 @@ struct userdata {
+         *source_output_move_finish_slot,
+         *sink_input_state_changed_slot,
+         *source_output_state_changed_slot;
++#ifdef USE_PM_LOCK
++    uint32_t pm_state;
++#endif /* USE_PM_LOCK */
+ };
+ struct device_info {
+@@ -80,10 +136,83 @@ struct device_info {
+     pa_time_event *time_event;
+     pa_usec_t timeout;
+ };
++#ifdef USE_PM_LOCK
++
++static int send_msg(unsigned int s_bits, unsigned int timeout, unsigned int timeout2)
++{
++      int rc = 0;
++      int sock;
++      struct pwr_msg p;
++      struct sockaddr_un remote;
++
++      p.pid = getpid();
++      p.cond = s_bits;
++      p.timeout = timeout;
++      p.timeout2 = timeout2;
++
++      sock = socket(AF_UNIX, SOCK_DGRAM, 0);
++      if (sock == -1) {
++              return -1;
++      }
++
++      remote.sun_family = AF_UNIX;
++      if(strlen(SOCK_PATH) >= sizeof(remote.sun_path)) {
++              return -1;
++      }
++      strncpy(remote.sun_path, SOCK_PATH, sizeof(remote.sun_path));
++
++      rc = sendto(sock, (void *)&p, sizeof(p), 0, (struct sockaddr *)&remote,
++                  sizeof(struct sockaddr_un));
++
++      close(sock);
++      return rc;
++}
++
++static int pm_lock_state(unsigned int s_bits, unsigned int flag,
++                    unsigned int timeout)
++{
++      switch (s_bits) {
++      case LCD_NORMAL:
++      case LCD_DIM:
++      case LCD_OFF:
++              break;
++      default:
++              return -1;
++      }
++      if (flag & GOTO_STATE_NOW)
++              /* if the flag is true, go to the locking state directly */
++              s_bits = s_bits | (s_bits << SHIFT_CHANGE_STATE);
++      if (flag & HOLD_KEY_BLOCK)
++              s_bits = s_bits | (1 << SHIFT_HOLD_KEY_BLOCK);
++
++      return send_msg(s_bits, timeout, 0);
++}
++
++static int pm_unlock_state(unsigned int s_bits, unsigned int flag)
++{
++      switch (s_bits) {
++      case LCD_NORMAL:
++      case LCD_DIM:
++      case LCD_OFF:
++              break;
++      default:
++              return -1;
++      }
++
++      s_bits = (s_bits << SHIFT_UNLOCK);
++      s_bits = (s_bits | (flag << SHIFT_UNLOCK_PARAMETER));
++      return send_msg(s_bits, 0, 0);
++}
++
++#endif
+ static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
+     struct device_info *d = userdata;
++#ifdef USE_PM_LOCK
++    int ret = -1;
++#endif
++
+     pa_assert(d);
+     d->userdata->core->mainloop->time_restart(d->time_event, NULL);
+@@ -92,12 +221,33 @@ static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval
+         pa_log_info("Sink %s idle for too long, suspending ...", d->sink->name);
+         pa_sink_suspend(d->sink, true, PA_SUSPEND_IDLE);
+         pa_core_maybe_vacuum(d->userdata->core);
++#ifdef USE_PM_LOCK
++              UPDATE_PM_UNLOCK(d->userdata->pm_state, PM_TYPE_SINK);
++              if(!(d->userdata->pm_state)) {
++                      ret = pm_unlock_state(LCD_OFF, PM_SLEEP_MARGIN);
++                      if(ret != -1)
++                              pa_log_info("sink pm_unlock_state success [%d]", ret);
++                      else
++                              pa_log_error("sink pm_unlock_state failed [%d]", ret);
++              }
++#endif /* USE_PM_LOCK */
+     }
+     if (d->source && pa_source_check_suspend(d->source) <= 0 && !(d->source->suspend_cause & PA_SUSPEND_IDLE)) {
+         pa_log_info("Source %s idle for too long, suspending ...", d->source->name);
+         pa_source_suspend(d->source, true, PA_SUSPEND_IDLE);
+         pa_core_maybe_vacuum(d->userdata->core);
++#ifdef USE_PM_LOCK
++
++        UPDATE_PM_UNLOCK(d->userdata->pm_state, PM_TYPE_SOURCE);
++        if(!(d->userdata->pm_state)) {
++                      ret = pm_unlock_state(LCD_OFF, PM_SLEEP_MARGIN);
++                      if(ret != -1)
++                              pa_log_info("source pm_unlock_state success [%d]", ret);
++                      else
++                              pa_log_error("source pm_unlock_state failed [%d]", ret);
++              }
++#endif /* USE_PM_LOCK */
+     }
+ }
+@@ -117,17 +267,39 @@ static void restart(struct device_info *d) {
+ }
+ static void resume(struct device_info *d) {
++#ifdef USE_PM_LOCK
++    int ret = -1;
++#endif
++
+     pa_assert(d);
+     d->userdata->core->mainloop->time_restart(d->time_event, NULL);
+     if (d->sink) {
+-        pa_log_debug("Sink %s becomes busy, resuming.", d->sink->name);
++#ifdef USE_PM_LOCK
++              UPDATE_PM_LOCK(d->userdata->pm_state, PM_TYPE_SINK);
++              ret = pm_lock_state(LCD_OFF, STAY_CUR_STATE, 0);
++              if(ret != -1) {
++                      pa_log_info("sink pm_lock_state success [%d]", ret);
++              } else {
++                      pa_log_error("sink pm_lock_state failed [%d]", ret);
++              }
++#endif /* USE_PM_LOCK */
++        pa_log_debug("Sink %s becomes busy.", d->sink->name);
+         pa_sink_suspend(d->sink, false, PA_SUSPEND_IDLE);
+     }
+     if (d->source) {
+-        pa_log_debug("Source %s becomes busy, resuming.", d->source->name);
++#ifdef USE_PM_LOCK
++              UPDATE_PM_LOCK(d->userdata->pm_state, PM_TYPE_SOURCE);
++              ret = pm_lock_state(LCD_OFF, STAY_CUR_STATE, 0);
++              if(ret != -1) {
++                      pa_log_info("source pm_lock_state success [%d]", ret);
++              } else {
++                      pa_log_error("source pm_lock_state failed [%d]", ret);
++              }
++#endif /* USE_PM_LOCK */
++        pa_log_debug("Source %s becomes busy.", d->source->name);
+         pa_source_suspend(d->source, false, PA_SUSPEND_IDLE);
+     }
+ }
+@@ -462,6 +634,9 @@ int pa__init(pa_module*m) {
+     u->core = m->core;
+     u->timeout = timeout * PA_USEC_PER_SEC;
+     u->device_infos = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL, (pa_free_cb_t) device_info_free);
++#ifdef USE_PM_LOCK
++    u->pm_state = 0x00;
++#endif /* USE_PM_LOCK */
+     PA_IDXSET_FOREACH(sink, m->core->sinks, idx)
+         device_new_hook_cb(m->core, PA_OBJECT(sink), u);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0035-Add-preload-fileter-for-resample-samsung.patch.gz b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0035-Add-preload-fileter-for-resample-samsung.patch.gz
new file mode 100644 (file)
index 0000000..a8f29d7
Binary files /dev/null and b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0035-Add-preload-fileter-for-resample-samsung.patch.gz differ
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0036-Enhance-for-echo-cancel-samsung.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0036-Enhance-for-echo-cancel-samsung.patch
new file mode 100644 (file)
index 0000000..19bbb16
--- /dev/null
@@ -0,0 +1,304 @@
+From: "vivian,zhang" <vivian.zhang@intel.com>
+Date: Tue, 18 Jun 2013 16:18:58 +0800
+Subject: Enhance for echo cancel - samsung
+
+Change-Id: Ibd59e7e033d5a6789ddc7d5ef39e23f26dcf55cc
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/Makefile.am                              |   2 +-
+ src/map-file                                 |   2 +
+ src/modules/echo-cancel/module-echo-cancel.c |  55 +++++++++++++++
+ src/pulse/ext-echo-cancel.c                  | 100 +++++++++++++++++++++++++++
+ src/pulse/ext-echo-cancel.h                  |  49 +++++++++++++
+ 5 files changed, 207 insertions(+), 1 deletion(-)
+ create mode 100644 src/pulse/ext-echo-cancel.c
+ create mode 100644 src/pulse/ext-echo-cancel.h
+
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 12bb73e..ae841e3 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -1950,7 +1950,7 @@ module_echo_cancel_la_SOURCES = \
+               modules/echo-cancel/null.c \
+               modules/echo-cancel/echo-cancel.h
+ module_echo_cancel_la_LDFLAGS = $(MODULE_LDFLAGS)
+-module_echo_cancel_la_LIBADD = $(MODULE_LIBADD) $(LIBSPEEX_LIBS)
++module_echo_cancel_la_LIBADD = libprotocol-native.la $(MODULE_LIBADD) $(LIBSPEEX_LIBS)
+ module_echo_cancel_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) $(LIBSPEEX_CFLAGS)
+ if HAVE_ADRIAN_EC
+ module_echo_cancel_la_SOURCES += \
+diff --git a/src/map-file b/src/map-file
+index 95364ae..d51596c 100644
+--- a/src/map-file
++++ b/src/map-file
+@@ -182,6 +182,8 @@ pa_ext_node_manager_connect_nodes;
+ pa_ext_node_manager_disconnect_nodes;
+ 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_format_info_copy;
+ pa_format_info_free;
+ pa_format_info_from_string;
+diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
+index fbdb3b3..29eed13 100644
+--- a/src/modules/echo-cancel/module-echo-cancel.c
++++ b/src/modules/echo-cancel/module-echo-cancel.c
+@@ -53,6 +53,9 @@
+ #include <pulsecore/sample-util.h>
+ #include <pulsecore/ltdl-helper.h>
++#include <pulsecore/protocol-native.h>
++#include <pulsecore/pstream-util.h>
++
+ #include "module-echo-cancel-symdef.h"
+ PA_MODULE_AUTHOR("Wim Taymans");
+@@ -94,6 +97,11 @@ typedef enum {
+ #endif
+ } pa_echo_canceller_method_t;
++enum {
++      AEC_SET_VOLUME,
++      AEC_SET_DEVICE,
++};
++
+ #ifdef HAVE_WEBRTC
+ #define DEFAULT_ECHO_CANCELLER "webrtc"
+ #else
+@@ -255,6 +263,8 @@ struct userdata {
+     struct {
+         pa_cvolume current_volume;
+     } thread_info;
++
++    pa_native_protocol *protocol;
+ };
+ static void source_output_snapshot_within_thread(struct userdata *u, struct snapshot *snapshot);
+@@ -1602,6 +1612,43 @@ static pa_echo_canceller_method_t get_ec_method_from_string(const char *method)
+     return PA_ECHO_CANCELLER_INVALID;
+ }
++static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) {
++      uint32_t command;
++      uint32_t value;
++      pa_tagstruct *reply = NULL;
++      pa_assert(p);
++      pa_assert(m);
++      pa_assert(c);
++      pa_assert(t);
++
++      if (pa_tagstruct_getu32(t, &command) < 0)
++      goto fail;
++
++      reply = pa_tagstruct_new(NULL, 0);
++      pa_tagstruct_putu32(reply, PA_COMMAND_REPLY);
++      pa_tagstruct_putu32(reply, tag);
++
++      switch (command) {
++              case AEC_SET_VOLUME: {
++                      pa_tagstruct_getu32(t,&value);
++                      pa_log_debug("AEC_SET_VOLUME in echo cancel = %d",value);
++              break;
++      }
++              case AEC_SET_DEVICE: {
++                      pa_tagstruct_getu32(t,&value);
++                      pa_log_debug("AEC_SET_DEVICE in echo cancel = %d",value);
++              break;
++      }
++      default:
++              goto fail;
++      }
++      pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply);
++      return 0;
++
++fail:
++      return -1;
++}
++
+ /* Common initialisation bits between module-echo-cancel and the standalone
+  * test program.
+  *
+@@ -1992,6 +2039,9 @@ int pa__init(pa_module*m) {
+     u->thread_info.current_volume = u->source->reference_volume;
++    u->protocol = pa_native_protocol_get(m->core);
++    pa_native_protocol_install_ext(u->protocol, m, extension_cb);
++
+     pa_sink_put(u->sink);
+     pa_source_put(u->source);
+@@ -2069,6 +2119,11 @@ void pa__done(pa_module*m) {
+         pa_xfree(u->ec);
+     }
++    if (u->protocol) {
++        pa_native_protocol_remove_ext(u->protocol, m);
++        pa_native_protocol_unref(u->protocol);
++    }
++
+     if (u->asyncmsgq)
+         pa_asyncmsgq_unref(u->asyncmsgq);
+diff --git a/src/pulse/ext-echo-cancel.c b/src/pulse/ext-echo-cancel.c
+new file mode 100644
+index 0000000..9339939
+--- /dev/null
++++ b/src/pulse/ext-echo-cancel.c
+@@ -0,0 +1,100 @@
++/***
++  This file is part of PulseAudio.
++
++  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 <pulse/context.h>
++#include <pulse/gccmacro.h>
++#include <pulse/xmalloc.h>
++
++#include <pulsecore/macro.h>
++#include <pulsecore/pstream-util.h>
++#include <pulsecore/log.h>
++#include "internal.h"
++#include "operation.h"
++#include "fork-detect.h"
++
++#include "ext-echo-cancel.h"
++
++enum {
++      AEC_SET_VOLUME,
++      AEC_SET_DEVICE,
++};
++
++pa_operation *pa_ext_echo_cancel_set_device (
++                              pa_context *c,
++                              int device,
++                              pa_context_success_cb_t cb,
++                              void *userdata) {
++
++      uint32_t tag;
++      pa_operation *o = NULL;
++      pa_tagstruct *t = NULL;
++
++      pa_assert(c);
++      pa_assert(PA_REFCNT_VALUE(c) >= 1);
++      PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++      PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++      PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED);
++
++      o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++      t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag);
++      pa_tagstruct_putu32(t, PA_INVALID_INDEX);
++      pa_tagstruct_puts(t, "module-echo-cancel");
++      pa_tagstruct_putu32(t, AEC_SET_DEVICE);
++      pa_tagstruct_putu32(t, device);
++
++      pa_pstream_send_tagstruct(c->pstream, t);
++      pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++      return o;
++}
++
++
++pa_operation *pa_ext_echo_cancel_set_volume (
++                              pa_context *c,
++                              int volume,
++                              pa_context_success_cb_t cb,
++                              void *userdata) {
++
++      uint32_t tag;
++      pa_operation *o = NULL;
++      pa_tagstruct *t = NULL;
++
++      pa_assert(c);
++      pa_assert(PA_REFCNT_VALUE(c) >= 1);
++      PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++      PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++      PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED);
++
++      o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++      t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag);
++      pa_tagstruct_putu32(t, PA_INVALID_INDEX);
++      pa_tagstruct_puts(t, "module-echo-cancel");
++      pa_tagstruct_putu32(t, AEC_SET_VOLUME);
++      pa_tagstruct_putu32(t, volume);
++
++      pa_pstream_send_tagstruct(c->pstream, t);
++      pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++      return o;
++}
+diff --git a/src/pulse/ext-echo-cancel.h b/src/pulse/ext-echo-cancel.h
+new file mode 100644
+index 0000000..12e4eeb
+--- /dev/null
++++ b/src/pulse/ext-echo-cancel.h
+@@ -0,0 +1,49 @@
++#ifndef foopulseechocancelfoo
++#define foopulseechocancelfoo
++
++/***
++  This file is part of PulseAudio.
++
++  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/context.h>
++#include <pulse/version.h>
++
++/** \file
++ *
++ * Routines for controlling module-echo-cancel
++ */
++
++PA_C_DECL_BEGIN
++
++/** Set volume to AEC module */
++pa_operation *pa_ext_echo_cancel_set_volume (
++        pa_context *c,
++        int volume,
++        pa_context_success_cb_t cb,
++        void *userdata);
++
++pa_operation *pa_ext_echo_cancel_set_device (
++        pa_context *c,
++        int device,
++        pa_context_success_cb_t cb,
++        void *userdata);
++
++
++PA_C_DECL_END
++
++#endif
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0037-add-support-for-dlog-samsung.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0037-add-support-for-dlog-samsung.patch
new file mode 100644 (file)
index 0000000..33c38c3
--- /dev/null
@@ -0,0 +1,285 @@
+From: "vivian,zhang" <vivian.zhang@intel.com>
+Date: Tue, 18 Jun 2013 16:20:04 +0800
+Subject: add support for dlog - samsung
+
+Change-Id: Ieddf2f3bdab50926372e9e2b5cedb2756b6cfd5c
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ configure.ac                | 18 +++++++++
+ src/Makefile.am             |  9 +++++
+ src/daemon/cmdline.c        |  8 +++-
+ src/daemon/daemon-conf.c    | 10 ++++-
+ src/pulsecore/cli-command.c |  8 ++++
+ src/pulsecore/log.c         | 95 +++++++++++++++++++++++++++++++++++++++++++++
+ src/pulsecore/log.h         |  4 ++
+ 7 files changed, 150 insertions(+), 2 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index 9a79b36..c0beac0 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -643,6 +643,24 @@ PKG_CHECK_MODULES(LIBJSON, [ json-c >= 0.11 ], [],
+ PKG_CHECK_MODULES(LIBSNDFILE, [ sndfile >= 1.0.20 ])
++dnl use dlog --------------------------------------------------------------------------
++AC_ARG_ENABLE(dlog, AC_HELP_STRING([--enable-dlog], [using dlog]),
++[
++ case "${enableval}" in
++       yes) USE_DLOG=yes ;;
++       no)  USE_DLOG=no ;;
++       *)   AC_MSG_ERROR(bad value ${enableval} for --enable-dlog) ;;
++ esac
++ ],[USE_DLOG=no])
++
++if test "x$USE_DLOG" = "xyes"; then
++      PKG_CHECK_MODULES(DLOG, dlog)
++      AC_SUBST(DLOG_CFLAGS)
++      AC_SUBST(DLOG_LIBS)
++fi
++AM_CONDITIONAL(USE_DLOG, test "x$USE_DLOG" = "xyes")
++dnl end --------------------------------------------------------------------
++
+ #### atomic-ops ####
+ AC_MSG_CHECKING([whether we need libatomic_ops])
+diff --git a/src/Makefile.am b/src/Makefile.am
+index ae841e3..3e41300 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -172,6 +172,10 @@ else
+ pulseaudio_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(IMMEDIATE_LDFLAGS) -dlopen force $(foreach f,$(PREOPEN_LIBS),-dlopen $(f))
+ endif
++if USE_DLOG
++pulseaudio_CFLAGS += -DUSE_DLOG
++endif
++
+ ###################################
+ #       Utility programs          #
+ ###################################
+@@ -740,6 +744,11 @@ libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS += $(DBUS_CFLAGS)
+ libpulsecommon_@PA_MAJORMINOR@_la_LIBADD += $(DBUS_LIBS)
+ endif
++if USE_DLOG
++libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS += $(DLOG_CFLAGS) -DUSE_DLOG
++libpulsecommon_@PA_MAJORMINOR@_la_LIBADD += $(DLOG_LIBS)
++endif
++
+ ###################################
+ #         Client library          #
+ ###################################
+diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c
+index 68579c5..5bb1a0a 100644
+--- a/src/daemon/cmdline.c
++++ b/src/daemon/cmdline.c
+@@ -140,8 +140,12 @@ void pa_cmdline_help(const char *argv0) {
+            "      --scache-idle-time=SECS           Unload autoloaded samples when idle and\n"
+            "                                        this time passed\n"
+            "      --log-level[=LEVEL]               Increase or set verbosity level\n"
+-           "  -v  --verbose                         Increase the verbosity level\n"
++           "  -v                                    Increase the verbosity level\n"
++#ifdef USE_DLOG
++           "      --log-target={auto,syslog,stderr,file:PATH,newfile:PATH,dlog,dlog-color}\n"
++#else
+            "      --log-target={auto,syslog,stderr,file:PATH,newfile:PATH}\n"
++#endif
+            "                                        Specify the log target\n"
+            "      --log-meta[=BOOL]                 Include code location in log messages\n"
+            "      --log-time[=BOOL]                 Include timestamps in log messages\n"
+@@ -325,6 +329,8 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d
+                 if (pa_daemon_conf_set_log_target(conf, optarg) < 0) {
+ #ifdef HAVE_JOURNAL
+                     pa_log(_("Invalid log target: use either 'syslog', 'journal','stderr' or 'auto' or a valid file name 'file:<path>', 'newfile:<path>'."));
++#elif defined(USE_DLOG)
++                    pa_log(_("Invalid log target: use either 'syslog', 'stderr' or 'auto' or a valid file name 'file:<path>', 'newfile:<path>' or 'dlog' or 'dlog-color'."));
+ #else
+                     pa_log(_("Invalid log target: use either 'syslog', 'stderr' or 'auto' or a valid file name 'file:<path>', 'newfile:<path>'."));
+ #endif
+diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
+index ce777a6..1dde213 100644
+--- a/src/daemon/daemon-conf.c
++++ b/src/daemon/daemon-conf.c
+@@ -188,9 +188,17 @@ int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string) {
+         if (!log_target)
+             return -1;
++
++        c->log_target = log_target;
+     }
+-    c->log_target = log_target;
++#ifdef USE_DLOG
++    else if (!strcmp(string, "dlog")) {
++        c->log_target = PA_LOG_DLOG;
++    } else if (!strcmp(string, "dlog-color")) {
++        c->log_target = PA_LOG_DLOG_COLOR;
++    }
++#endif
+     return 0;
+ }
+diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c
+index 8c956ac..2497b41 100644
+--- a/src/pulsecore/cli-command.c
++++ b/src/pulsecore/cli-command.c
+@@ -188,7 +188,11 @@ static const struct command commands[] = {
+     { "kill-client",             pa_cli_command_kill_client,        "Kill a client (args: index)", 2},
+     { "kill-sink-input",         pa_cli_command_kill_sink_input,    "Kill a sink input (args: index)", 2},
+     { "kill-source-output",      pa_cli_command_kill_source_output, "Kill a source output (args: index)", 2},
++#ifdef USE_DLOG
++    { "set-log-target",          pa_cli_command_log_target,         "Change the log target (args: null|auto|syslog|stderr|file:PATH|newfile:PATH|dlog|dlog-color)", 2},
++#else
+     { "set-log-target",          pa_cli_command_log_target,         "Change the log target (args: null|auto|syslog|stderr|file:PATH|newfile:PATH)", 2},
++#endif
+     { "set-log-level",           pa_cli_command_log_level,          "Change the log level (args: numeric level)", 2},
+     { "set-log-meta",            pa_cli_command_log_meta,           "Show source code location in log messages (args: bool)", 2},
+     { "set-log-time",            pa_cli_command_log_time,           "Show timestamps in log messages (args: bool)", 2},
+@@ -1508,7 +1512,11 @@ static int pa_cli_command_log_target(pa_core *c, pa_tokenizer *t, pa_strbuf *buf
+     pa_assert(fail);
+     if (!(m = pa_tokenizer_get(t, 1))) {
++#ifdef USE_DLOG
++        pa_strbuf_puts(buf, "You need to specify a log target (null|auto|syslog|stderr|file:PATH|newfile:PATH|dlog|dlog-color).\n");
++#else
+         pa_strbuf_puts(buf, "You need to specify a log target (null|auto|syslog|stderr|file:PATH|newfile:PATH).\n");
++#endif
+         return -1;
+     }
+diff --git a/src/pulsecore/log.c b/src/pulsecore/log.c
+index cf96dce..dfb1d0f 100644
+--- a/src/pulsecore/log.c
++++ b/src/pulsecore/log.c
+@@ -61,6 +61,27 @@
+ #include "log.h"
++#ifdef USE_DLOG
++#include <dlog.h>
++#define       DLOG_TAG        "PULSEAUDIO"
++
++#define COLOR_BLACK           30
++#define COLOR_RED             31
++#define COLOR_GREEN           32
++#define COLOR_BLUE            34
++#define COLOR_MAGENTA         35
++#define COLOR_CYAN            36
++#define COLOR_WHITE           97
++#define COLOR_B_GRAY          100
++#define COLOR_B_RED           101
++#define COLOR_B_GREEN         102
++#define COLOR_B_YELLOW                103
++#define COLOR_B_BLUE          104
++#define COLOR_B_MAGENTA       105
++#define COLOR_B_CYAN          106
++#define COLOR_REVERSE         7
++
++#endif
+ #define ENV_LOG_SYSLOG "PULSE_LOG_SYSLOG"
+ #define ENV_LOG_LEVEL "PULSE_LOG"
+ #define ENV_LOG_COLORS "PULSE_LOG_COLORS"
+@@ -545,6 +566,74 @@ void pa_log_levelv_meta(
+                 break;
+             }
++
++#ifdef USE_DLOG
++            case PA_LOG_DLOG: {
++                char *local_t;
++
++                openlog(ident, LOG_PID, LOG_USER);
++
++                if ((local_t = pa_utf8_to_locale(t)))
++                    t = local_t;
++
++                switch (level)
++                {
++                                      case PA_LOG_DEBUG:
++                                              SLOG (LOG_DEBUG, DLOG_TAG, "%s%s%s%s",  timestamp, location, t, pa_strempty(bt));
++                                              break;
++                                      case PA_LOG_INFO:
++                                      case PA_LOG_NOTICE:     // no notice category in dlog, use info instead.
++                                              SLOG (LOG_INFO, DLOG_TAG, "%s%s%s%s", timestamp, location, t, pa_strempty(bt));
++                                              break;
++                                      case PA_LOG_WARN:
++                                              SLOG (LOG_WARN, DLOG_TAG, "%s%s%s%s", timestamp, location, t, pa_strempty(bt));
++                                              break;
++                                      case PA_LOG_ERROR:
++                                              SLOG (LOG_ERROR, DLOG_TAG, "%s%s%s%s", timestamp, location, t, pa_strempty(bt));
++                                              break;
++                                      default:
++                                              SLOG (LOG_DEBUG, DLOG_TAG, "%s%s%s%s", timestamp, location, t, pa_strempty(bt));
++                                              break;
++                }
++
++                pa_xfree(local_t);
++
++                break;
++            }
++            case PA_LOG_DLOG_COLOR: {
++                              char *local_t;
++
++                              openlog(ident, LOG_PID, LOG_USER);
++
++                              if ((local_t = pa_utf8_to_locale(t)))
++                                      t = local_t;
++
++                              switch (level)
++                              {
++                                      case PA_LOG_DEBUG:
++                                              SLOG (LOG_DEBUG, DLOG_TAG, "\033[%dm%s%s%s%s\033[0m", COLOR_GREEN, timestamp, location, t, pa_strempty(bt));
++                                              break;
++                                      case PA_LOG_INFO:
++                                      case PA_LOG_NOTICE:     // no notice category in dlog, use info instead.
++                                              SLOG (LOG_INFO, DLOG_TAG, "\033[%dm%s%s%s%s\033[0m", COLOR_BLUE, timestamp, location, t, pa_strempty(bt));
++                                              break;
++                                      case PA_LOG_WARN:
++                                              SLOG (LOG_WARN, DLOG_TAG, "\033[%dm%s%s%s%s\033[0m", COLOR_MAGENTA, timestamp, location, t, pa_strempty(bt));
++                                              break;
++                                      case PA_LOG_ERROR:
++                                              SLOG (LOG_ERROR, DLOG_TAG, "\033[%dm%s%s%s%s\033[0m", COLOR_RED, timestamp, location, t, pa_strempty(bt));
++                                              break;
++                                      default:
++                                              SLOG (LOG_DEBUG, DLOG_TAG, "%s%s%s%s", timestamp, location, t, pa_strempty(bt));
++                                              break;
++                              }
++
++                              pa_xfree(local_t);
++
++                              break;
++                      }
++
++#endif
+             case PA_LOG_NULL:
+             default:
+                 break;
+@@ -629,6 +718,12 @@ pa_log_target *pa_log_parse_target(const char *string) {
+         t = pa_log_target_new(PA_LOG_FILE, string + 5);
+     else if (pa_startswith(string, "newfile:"))
+         t = pa_log_target_new(PA_LOG_NEWFILE, string + 8);
++#ifdef USE_DLOG
++    else if (pa_streq(string, "dlog"))
++        t = pa_log_target_new(PA_LOG_DLOG, NULL);
++    else if (pa_streq(string, "dlog-color"))
++        t = pa_log_target_new(PA_LOG_DLOG_COLOR, NULL);
++#endif
+     else
+         pa_log(_("Invalid log target."));
+diff --git a/src/pulsecore/log.h b/src/pulsecore/log.h
+index 5e9611d..031040f 100644
+--- a/src/pulsecore/log.h
++++ b/src/pulsecore/log.h
+@@ -41,6 +41,10 @@ typedef enum pa_log_target_type {
+     PA_LOG_NULL,        /* to /dev/null */
+     PA_LOG_FILE,        /* to a user specified file */
+     PA_LOG_NEWFILE,     /* with an automatic suffix to avoid overwriting anything */
++#ifdef USE_DLOG
++    PA_LOG_DLOG,
++    PA_LOG_DLOG_COLOR,
++#endif
+ } pa_log_target_type_t;
+ typedef enum pa_log_level {
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0038-add-policy-module-samsung.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0038-add-policy-module-samsung.patch
new file mode 100644 (file)
index 0000000..fb976b1
--- /dev/null
@@ -0,0 +1,1317 @@
+From: "vivian,zhang" <vivian.zhang@intel.com>
+Date: Tue, 18 Jun 2013 16:21:32 +0800
+Subject: add policy module - samsung
+
+Change-Id: I2111a9c4dc0a371dbea5b347cf77adbe8f930528
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ configure.ac                |  18 +
+ src/Makefile.am             |  28 +-
+ src/modules/module-policy.c | 926 ++++++++++++++++++++++++++++++++++++++++++++
+ src/pulse/ext-policy.c      | 177 +++++++++
+ src/pulse/ext-policy.h      |  61 +++
+ src/pulse/proplist.h        |   3 +
+ 6 files changed, 1211 insertions(+), 2 deletions(-)
+ create mode 100644 src/modules/module-policy.c
+ create mode 100644 src/pulse/ext-policy.c
+ create mode 100644 src/pulse/ext-policy.h
+
+diff --git a/configure.ac b/configure.ac
+index c0beac0..0e205d3 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -643,6 +643,24 @@ PKG_CHECK_MODULES(LIBJSON, [ json-c >= 0.11 ], [],
+ PKG_CHECK_MODULES(LIBSNDFILE, [ sndfile >= 1.0.20 ])
++dnl use samsung policy module --------------------------------------------------------
++AC_ARG_ENABLE(samsung-policy, AC_HELP_STRING([--enable-samsung-policy], [using samsung-policy]),
++[
++ case "${enableval}" in
++       yes) USE_SAMSUNG_POLICY=yes ;;
++       no)  USE_SAMSUNG_POLICY=no ;;
++       *)   AC_MSG_ERROR(bad value ${enableval} for --enable-samsung_policy) ;;
++ esac
++ ],[USE_SAMSUNG_POLICY=no])
++
++if test "x$USE_SAMSUNG_POLICY" = "xyes"; then
++      PKG_CHECK_MODULES(VCONF, vconf)
++      AC_SUBST(VCONF_CFLAGS)
++      AC_SUBST(VCONF_LIBS)
++fi
++AM_CONDITIONAL(USE_SAMSUNG_POLICY, test "x$USE_SAMSUNG_POLICY" = "xyes")
++dnl end --------------------------------------------------------------------
++
+ dnl use dlog --------------------------------------------------------------------------
+ AC_ARG_ENABLE(dlog, AC_HELP_STRING([--enable-dlog], [using dlog]),
+ [
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 3e41300..4872dfd 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -149,7 +149,7 @@ pulseaudio_SOURCES = \
+               daemon/ltdl-bind-now.c daemon/ltdl-bind-now.h \
+               daemon/main.c
+-pulseaudio_CFLAGS = $(AM_CFLAGS) $(CAP_CFLAGS)
++pulseaudio_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) $(CAP_CFLAGS) $(DBUS_CFLAGS)
+ pulseaudio_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la $(LIBLTDL) $(CAP_LIBS)
+ # This is needed because automake doesn't properly expand the foreach below
+ pulseaudio_DEPENDENCIES = libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la $(PREOPEN_LIBS)
+@@ -787,6 +787,11 @@ pulseinclude_HEADERS = \
+               pulse/volume.h \
+               pulse/xmalloc.h
++if USE_SAMSUNG_POLICY
++pulseinclude_HEADERS += \
++              pulse/ext-policy.h
++endif
++
+ lib_LTLIBRARIES = \
+               libpulse.la \
+               libpulse-simple.la
+@@ -833,6 +838,11 @@ libpulse_la_SOURCES = \
+               pulse/volume.c pulse/volume.h \
+               pulse/xmalloc.c pulse/xmalloc.h
++if USE_SAMSUNG_POLICY
++libpulse_la_SOURCES += \
++        pulse/ext-policy.c pulse/ext-policy.h
++endif
++
+ libpulse_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(LIBJSON_CFLAGS)
+ libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBJSON_LIBS) libpulsecommon-@PA_MAJORMINOR@.la
+ libpulse_la_LDFLAGS = $(AM_LDFLAGS) $(VERSIONING_LDFLAGS) -version-info $(LIBPULSE_VERSION_INFO)
+@@ -1088,6 +1098,10 @@ if HAVE_DBUS
+ # Serveral module (e.g. libalsa-util.la)
+ modlibexec_LTLIBRARIES += \
+               module-console-kit.la
++if USE_SAMSUNG_POLICY
++modlibexec_LTLIBRARIES += \
++              module-policy.la
++endif
+ endif
+ modlibexec_LTLIBRARIES += \
+@@ -1470,7 +1484,10 @@ SYMDEF_FILES = \
+               module-switch-on-port-available-symdef.h \
+               module-filter-apply-symdef.h \
+               module-filter-heuristics-symdef.h
+-
++if USE_SAMSUNG_POLICY
++SYMDEF_FILES += \
++              module-policy-symdef.h
++endif
+ if HAVE_ESOUND
+ SYMDEF_FILES += \
+               module-esound-protocol-tcp-symdef.h \
+@@ -2120,6 +2137,13 @@ module_rygel_media_server_la_LDFLAGS = $(MODULE_LDFLAGS)
+ module_rygel_media_server_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libprotocol-http.la
+ module_rygel_media_server_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
++if USE_SAMSUNG_POLICY
++module_policy_la_SOURCES = modules/module-policy.c
++module_policy_la_LDFLAGS = $(MODULE_LDFLAGS)
++module_policy_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) $(VCONF_LIBS) libprotocol-native.la libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la
++module_policy_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(VCONF_CFLAGS)
++endif
++
+ ###################################
+ #        Some minor stuff         #
+ ###################################
+diff --git a/src/modules/module-policy.c b/src/modules/module-policy.c
+new file mode 100644
+index 0000000..2172018
+--- /dev/null
++++ b/src/modules/module-policy.c
+@@ -0,0 +1,926 @@
++#ifdef HAVE_CONFIG_H
++#include <config.h>
++#endif
++
++#include <pulsecore/core.h>
++#include <pulsecore/module.h>
++#include <pulsecore/modargs.h>
++#include <pulsecore/core-rtclock.h>
++#include <pulsecore/core-util.h>
++#include <pulsecore/log.h>
++#include <stdbool.h>
++#include <strings.h>
++
++#include <pulsecore/log.h>
++#include <pulsecore/core-subscribe.h>
++#include <pulsecore/sink-input.h>
++#include <pulsecore/source-output.h>
++#include <pulsecore/namereg.h>
++#include <pulsecore/core-error.h>
++
++#include <pulsecore/protocol-native.h>
++#include <pulsecore/pstream-util.h>
++#include <vconf.h> // for mono
++
++#include "module-policy-symdef.h"
++
++PA_MODULE_AUTHOR("Seungbae Shin");
++PA_MODULE_DESCRIPTION("Media Policy module");
++PA_MODULE_VERSION(PACKAGE_VERSION);
++PA_MODULE_LOAD_ONCE(true);
++PA_MODULE_USAGE(
++        "on_hotplug=<When new device becomes available, recheck streams?> ");
++
++static const char* const valid_modargs[] = {
++    "on_hotplug",
++    NULL
++};
++
++struct userdata {
++    pa_core *core;
++    pa_module *module;
++
++    pa_hook_slot *sink_input_new_hook_slot,*sink_put_hook_slot;
++
++    pa_hook_slot *sink_input_unlink_slot,*sink_unlink_slot;
++    pa_hook_slot *sink_input_unlink_post_slot, *sink_unlink_post_slot;
++    pa_hook_slot *sink_input_move_start_slot,*sink_input_move_finish_slot;
++    pa_subscription *subscription;
++
++    bool on_hotplug:1;
++    int       bt_off_idx;
++
++    int is_mono;
++    float balance;
++    pa_module* module_mono_bt;
++    pa_module* module_combined;
++    pa_module* module_mono_combined;
++    pa_native_protocol *protocol;
++    pa_hook_slot *source_output_new_hook_slot;
++};
++
++enum {
++    SUBCOMMAND_TEST,
++    SUBCOMMAND_MONO,
++    SUBCOMMAND_BALANCE,
++};
++
++/* DEFINEs */
++#define AEC_SINK                      "alsa_output.0.analog-stereo.echo-cancel"
++#define AEC_SOURCE                    "alsa_input.0.analog-stereo.echo-cancel"
++#define       SINK_ALSA                       "alsa_output.0.analog-stereo"
++#define SINK_MONO_ALSA                "mono_alsa"
++#define SINK_MONO_BT          "mono_bt"
++#define SINK_COMBINED         "combined"
++#define SINK_MONO_COMBINED    "mono_combined"
++#define POLICY_AUTO                   "auto"
++#define POLICY_PHONE          "phone"
++#define POLICY_ALL                    "all"
++#define POLICY_VOIP                   "voip"
++#define BLUEZ_API                     "bluez"
++#define ALSA_API                      "alsa"
++#define MONO_KEY                      VCONFKEY_SETAPPL_ACCESSIBILITY_MONO_AUDIO
++
++/* check if this sink is bluez */
++static bool policy_is_bluez (pa_sink* sink)
++{
++      const char* api_name = NULL;
++
++      if (sink == NULL) {
++              pa_log_warn ("input param sink is null");
++              return false;
++      }
++
++    api_name = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_API);
++      if (api_name) {
++              if (pa_streq (api_name, BLUEZ_API)) {
++#ifdef DEBUG_DETAIL
++                      pa_log_debug("[POLICY][%s] [%s] exists and it is [%s]...true !!", __func__, PA_PROP_DEVICE_API, api_name);
++#endif
++                      return true;
++              } else {
++#ifdef DEBUG_DETAIL
++                      pa_log_debug("[POLICY][%s] [%s] exists, but not bluez...false !!", __func__, PA_PROP_DEVICE_API);
++#endif
++              }
++      } else {
++#ifdef DEBUG_DETAIL
++              pa_log_debug("[POLICY][%s] No [%s] exists...false!!", __func__, PA_PROP_DEVICE_API);
++#endif
++      }
++
++      return false;
++}
++
++/* check if this sink is bluez */
++static bool policy_is_usb_alsa (pa_sink* sink)
++{
++      const char* api_name = NULL;
++      const char* device_bus_name = NULL;
++
++      if (sink == NULL) {
++              pa_log_warn ("input param sink is null");
++              return false;
++      }
++
++    api_name = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_API);
++      if (api_name) {
++              if (pa_streq (api_name, ALSA_API)) {
++#ifdef DEBUG_DETAIL
++                      pa_log_debug("[POLICY][%s] [%s] exists and it is [%s]...true !!", __func__, PA_PROP_DEVICE_API, api_name);
++#endif
++                      device_bus_name = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_BUS);
++                      if (device_bus_name) {
++                              if (pa_streq (device_bus_name, "usb")) {
++                                      return true;
++                              }
++                      }
++              } else {
++#ifdef DEBUG_DETAIL
++                      pa_log_debug("[POLICY][%s] [%s] exists, but not alsa...false !!", __func__, PA_PROP_DEVICE_API);
++#endif
++              }
++      } else {
++#ifdef DEBUG_DETAIL
++              pa_log_debug("[POLICY][%s] No [%s] exists...false!!", __func__, PA_PROP_DEVICE_API);
++#endif
++      }
++
++      return false;
++}
++
++/* Get sink by name */
++static pa_sink* policy_get_sink_by_name (pa_core *c, const char* sink_name)
++{
++    pa_sink *s = NULL;
++    uint32_t idx;
++
++    if (c == NULL || sink_name == NULL) {
++              pa_log_warn ("input param is null");
++              return NULL;
++    }
++
++      PA_IDXSET_FOREACH(s, c->sinks, idx) {
++              if (pa_streq (s->name, sink_name)) {
++                      pa_log_debug ("[POLICY][%s] return [%p] for [%s]\n",  __func__, s, sink_name);
++                      return s;
++              }
++      }
++      return NULL;
++}
++
++/* Get bt sink if available */
++static pa_sink* policy_get_bt_sink (pa_core *c)
++{
++    pa_sink *s = NULL;
++    uint32_t idx;
++
++    if (c == NULL) {
++              pa_log_warn ("input param is null");
++              return NULL;
++    }
++
++      PA_IDXSET_FOREACH(s, c->sinks, idx) {
++              if (policy_is_bluez (s)) {
++                      pa_log_debug ("[POLICY][%s] return [%p] for [%s]\n", __func__, s, s->name);
++                      return s;
++              }
++      }
++      return NULL;
++}
++
++/* Select sink for given condition */
++static pa_sink* policy_select_proper_sink (pa_core *c, const char* policy, int is_mono)
++{
++      pa_sink* sink = NULL;
++      pa_sink* bt_sink = NULL;
++      pa_sink* def = NULL;
++
++      if (c == NULL || policy == NULL) {
++              pa_log_warn ("input param is null");
++              return NULL;
++      }
++
++      pa_assert (c);
++
++      bt_sink = policy_get_bt_sink(c);
++      def = pa_namereg_get_default_sink(c);
++      if (def == NULL) {
++              pa_log_warn ("POLICY][%s] pa_namereg_get_default_sink() returns null", __func__);
++              return NULL;
++      }
++
++      pa_log_debug ("[POLICY][%s] policy[%s], is_mono[%d], current default[%s], bt sink[%s]\n",
++                      __func__, policy, is_mono, def->name, (bt_sink)? bt_sink->name:"null");
++
++      /* Select sink to */
++      if (pa_streq(policy, POLICY_ALL)) {
++              /* all */
++              if (bt_sink) {
++                      sink = policy_get_sink_by_name(c, (is_mono)? SINK_MONO_COMBINED : SINK_COMBINED);
++              } else {
++                      sink = policy_get_sink_by_name (c, (is_mono)? SINK_MONO_ALSA : SINK_ALSA);
++              }
++
++      } else if (pa_streq(policy, POLICY_PHONE)) {
++              /* phone */
++              sink = policy_get_sink_by_name (c, (is_mono)? SINK_MONO_ALSA : SINK_ALSA);
++      } else if (pa_streq(policy, POLICY_VOIP)) {
++              /* VOIP */
++              sink = policy_get_sink_by_name (c,AEC_SINK);
++      } else {
++              /* auto */
++              if (policy_is_bluez(def)) {
++                      sink = (is_mono)? policy_get_sink_by_name (c, SINK_MONO_BT) : def;
++              } else if (policy_is_usb_alsa(def)) {
++                      sink = def;
++              } else {
++                      sink = (is_mono)? policy_get_sink_by_name (c, SINK_MONO_ALSA) : def;
++              }
++      }
++
++      pa_log_debug ("[POLICY][%s] selected sink : [%s]\n", __func__, (sink)? sink->name : "null");
++      return sink;
++}
++
++static bool policy_is_filter (pa_sink_input* si)
++{
++      const char* role = NULL;
++
++      if (si == NULL) {
++              pa_log_warn ("input param sink-input is null");
++              return false;
++      }
++
++      if ((role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) {
++#ifdef DEBUG_DETAIL
++              pa_log_debug("[POLICY][%s] Role of sink input [%d] = %s", __func__, si->index, role);
++#endif
++              if (pa_streq(role, "filter")) {
++#ifdef DEBUG_DETAIL
++                      pa_log_debug("[POLICY] no need to change of sink for %s", role);
++#endif
++                      return true;
++              }
++      }
++
++      return false;
++}
++
++
++
++#define EXT_VERSION 1
++
++static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) {
++  struct userdata *u = NULL;
++  uint32_t command;
++  pa_tagstruct *reply = NULL;
++
++  pa_sink_input *si = NULL;
++  pa_sink *s = NULL;
++  uint32_t idx;
++  pa_sink* sink_to_move  = NULL;
++
++  pa_assert(p);
++  pa_assert(m);
++  pa_assert(c);
++  pa_assert(t);
++
++  u = m->userdata;
++
++  if (pa_tagstruct_getu32(t, &command) < 0)
++    goto fail;
++
++  reply = pa_tagstruct_new(NULL, 0);
++  pa_tagstruct_putu32(reply, PA_COMMAND_REPLY);
++  pa_tagstruct_putu32(reply, tag);
++
++  switch (command) {
++    case SUBCOMMAND_TEST: {
++              if (!pa_tagstruct_eof(t))
++                      goto fail;
++
++              pa_tagstruct_putu32(reply, EXT_VERSION);
++              break;
++    }
++
++    case SUBCOMMAND_MONO: {
++
++        bool enable;
++
++        if (pa_tagstruct_get_boolean(t, &enable) < 0)
++            goto fail;
++
++        pa_log_debug ("[POLICY][%s] new mono value = %d\n", __func__, enable);
++        if (enable == u->is_mono) {
++                      pa_log_debug ("[POLICY][%s] No changes in mono value = %d", __func__, u->is_mono);
++                      break;
++        }
++
++        u->is_mono = enable;
++
++              /* Move current sink-input to proper mono sink */
++              PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) {
++                      const char *policy = NULL;
++
++                      /* Skip this if it is already in the process of being moved
++                       * anyway */
++                      if (!si->sink)
++                              continue;
++
++                      /* It might happen that a stream and a sink are set up at the
++                         same time, in which case we want to make sure we don't
++                         interfere with that */
++                      if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si)))
++                              continue;
++
++                      /* Get role (if role is filter, skip it) */
++                      if (policy_is_filter(si))
++                              continue;
++
++                      /* Check policy, if no policy exists, treat as AUTO */
++                      if (!(policy = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_POLICY))) {
++                              pa_log_debug("[POLICY] set policy of sink-input[%d] from [%s] to [auto]", si->index, "null");
++                              policy  = POLICY_AUTO;
++                      }
++                      pa_log_debug("[POLICY] Policy of sink input [%d] = %s", si->index, policy);
++
++                      /* Select sink to move and move to it */
++                      sink_to_move = policy_select_proper_sink (u->core, policy, u->is_mono);
++                      if (sink_to_move) {
++                              pa_log_debug("[POLICY][%s] Moving sink-input[%d] from [%s] to [%s]", __func__, si->index, si->sink->name, sink_to_move->name);
++                              pa_sink_input_move_to(si, sink_to_move, false);
++                      } else {
++                              pa_log_debug("[POLICY][%s] Can't move sink-input....", __func__);
++                      }
++              }
++        break;
++    }
++
++    case SUBCOMMAND_BALANCE: {
++              float balance;
++              pa_cvolume cvol;
++              pa_channel_map map;
++
++              if (pa_tagstruct_get_cvolume(t, &cvol) < 0)
++                      goto fail;
++
++              pa_channel_map_init_stereo(&map);
++              balance = pa_cvolume_get_balance(&cvol, &map);
++
++              pa_log_debug ("[POLICY][%s] new balance value = [%f]\n", __func__, balance);
++
++              if (balance == u->balance) {
++                      pa_log_debug ("[POLICY][%s] No changes in balance value = [%f]", __func__, u->balance);
++                      break;
++              }
++
++              u->balance = balance;
++
++              /* Apply balance value to each Sinks */
++              PA_IDXSET_FOREACH(s, u->core->sinks, idx) {
++                      pa_cvolume* cvol = pa_sink_get_volume (s, false);
++                      pa_cvolume_set_balance (cvol, &s->channel_map, u->balance);
++                      pa_sink_set_volume(s, cvol, true, true);
++              }
++              break;
++      }
++
++    default:
++      goto fail;
++  }
++
++  pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply);
++  return 0;
++
++  fail:
++
++  if (reply)
++        pa_tagstruct_free(reply);
++
++  return -1;
++}
++
++/*  Called when new sink-input is creating  */
++static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u)
++{
++    const char *policy = NULL;
++
++    pa_assert(c);
++    pa_assert(new_data);
++    pa_assert(u);
++
++    if (!new_data->proplist) {
++        pa_log_debug("[POLICY] New stream lacks property data.");
++        return PA_HOOK_OK;
++    }
++
++    /* If sink-input has already sink, skip */
++    if (new_data->sink) {
++      /* sink-input with filter role will be also here because sink is already set */
++#ifdef DEBUG_DETAIL
++        pa_log_debug("[POLICY] Not setting device for stream [%s], because already set.",
++                      pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME)));
++#endif
++        return PA_HOOK_OK;
++    }
++
++    /* If no policy exists, skip */
++    if (!(policy = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_POLICY))) {
++        pa_log_debug("[POLICY][%s] Not setting device for stream [%s], because it lacks policy.",
++                      __func__, pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME)));
++        return PA_HOOK_OK;
++    }
++    pa_log_debug("[POLICY][%s] Policy for stream [%s] = [%s]",
++              __func__, pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME)), policy);
++
++    /* Set proper sink to sink-input */
++    pa_sink* new_sink = policy_select_proper_sink(c, policy, u->is_mono);
++    if(new_sink != new_data->sink)
++    {
++        pa_sink_input_new_data_set_sink(new_data, new_sink, false);
++    }
++      /*new_data->save_sink = false;
++      new_data->sink = policy_select_proper_sink (c, policy, u->is_mono);*/
++      pa_log_debug("[POLICY][%s] set sink of sink-input to [%s]", __func__, (new_data->sink)? new_data->sink->name : "null");
++
++    return PA_HOOK_OK;
++}
++
++/*  Called when new sink is added while sink-input is existing  */
++static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u)
++{
++    pa_sink_input *si;
++    pa_sink *sink_to_move;
++    uint32_t idx;
++    char *args = NULL;
++
++    bool is_bt;
++    bool is_usb_alsa;
++
++    pa_assert(c);
++    pa_assert(sink);
++    pa_assert(u);
++    pa_assert(u->on_hotplug);
++
++    /* If connected sink is BLUETOOTH, set as default */
++    /* we are checking with device.api property */
++    is_bt = policy_is_bluez(sink);
++    is_usb_alsa = policy_is_usb_alsa(sink);
++
++      if (is_bt || is_usb_alsa) {
++              pa_log_debug("[POLICY][%s] set default sink to sink[%s][%d]", __func__, sink->name, sink->index);
++              pa_namereg_set_default_sink (c,sink);
++      } else {
++              pa_log_debug("[POLICY][%s] this sink [%s][%d] is not a bluez....return", __func__, sink->name, sink->index);
++              return PA_HOOK_OK;
++      }
++
++      if (is_bt) {
++              /* Load mono_bt sink */
++              args = pa_sprintf_malloc("sink_name=%s master=%s channels=1", SINK_MONO_BT, sink->name);
++              u->module_mono_bt = pa_module_load(u->module->core, "module-remap-sink", args);
++              pa_xfree(args);
++
++              /* load combine sink */
++              args = pa_sprintf_malloc("sink_name=%s slaves=\"%s,%s\"", SINK_COMBINED, sink->name, SINK_ALSA);
++              u->module_combined = pa_module_load(u->module->core, "module-combine", args);
++              pa_xfree(args);
++
++              /* load mono_combine sink */
++              args = pa_sprintf_malloc("sink_name=%s master=%s channels=1", SINK_MONO_COMBINED, SINK_COMBINED);
++              u->module_mono_combined = pa_module_load(u->module->core, "module-remap-sink", args);
++              pa_xfree(args);
++      }
++
++      /* Iterate each sink inputs to decide whether we should move to new sink */
++    PA_IDXSET_FOREACH(si, c->sink_inputs, idx) {
++        const char *policy = NULL;
++
++        if (si->sink == sink)
++              continue;
++
++        /* Skip this if it is already in the process of being moved
++         * anyway */
++        if (!si->sink)
++            continue;
++
++        /* It might happen that a stream and a sink are set up at the
++           same time, in which case we want to make sure we don't
++           interfere with that */
++        if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si)))
++            continue;
++
++              /* Get role (if role is filter, skip it) */
++        if (policy_is_filter(si))
++              continue;
++
++              /* Check policy */
++              if (!(policy = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_POLICY))) {
++                      /* No policy exists, this means auto */
++                      pa_log_debug("[POLICY][%s] set policy of sink-input[%d] from [%s] to [auto]", __func__, si->index, "null");
++                      policy = POLICY_AUTO;
++              }
++
++              sink_to_move = policy_select_proper_sink (c, policy, u->is_mono);
++              if (sink_to_move) {
++                      pa_log_debug("[POLICY][%s] Moving sink-input[%d] from [%s] to [%s]", __func__, si->index, si->sink->name, sink_to_move->name);
++                      pa_sink_input_move_to(si, sink_to_move, false);
++              } else {
++                      pa_log_debug("[POLICY][%s] Can't move sink-input....",__func__);
++              }
++    }
++
++      /* Reset sink volume with balance from userdata */
++      pa_cvolume* cvol = pa_sink_get_volume(sink, false);
++      pa_cvolume_set_balance(cvol, &sink->channel_map, u->balance);
++      pa_sink_set_volume(sink, cvol, true, true);
++
++    return PA_HOOK_OK;
++}
++
++static void subscribe_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
++{
++    struct userdata *u = userdata;
++    pa_sink *def;
++    pa_sink_input *si;
++    uint32_t idx2;
++    pa_sink *sink_to_move = NULL;
++    pa_assert(u);
++
++    pa_log_debug("[POLICY][%s] subscribe_cb() t=[0x%x], idx=[%d]", __func__, t, idx);
++
++    /* We only handle server changes */
++    if (t == (PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE)) {
++
++      def = pa_namereg_get_default_sink(c);
++              if (def == NULL) {
++                      pa_log_warn("[POLICY][%s] pa_namereg_get_default_sink() returns null", __func__);
++                      return;
++              }
++      pa_log_debug("[POLICY][%s] trying to move stream to current default sink = [%s]", __func__, def->name);
++
++      /* Iterate each sink inputs to decide whether we should move to new DEFAULT sink */
++      PA_IDXSET_FOREACH(si, c->sink_inputs, idx2) {
++                      const char *policy = NULL;
++
++                      if (!si->sink)
++                              continue;
++
++                      /* Get role (if role is filter, skip it) */
++                      if (policy_is_filter(si))
++                              continue;
++
++                      /* Get policy */
++                      if (!(policy = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_POLICY))) {
++                              /* No policy exists, this means auto */
++                              pa_log_debug("[POLICY][%s] set policy of sink-input[%d] from [%s] to [auto]", __func__, si->index, "null");
++                              policy = POLICY_AUTO;
++                      }
++
++                      sink_to_move = policy_select_proper_sink (c, policy, u->is_mono);
++                      if (sink_to_move) {
++                              /* Move sink-input to new DEFAULT sink */
++                              pa_log_debug("[POLICY][%s] Moving sink-input[%d] from [%s] to [%s]", __func__, si->index, si->sink->name, sink_to_move->name);
++                              pa_sink_input_move_to(si, sink_to_move, false);
++                      }
++      }
++    }
++}
++
++static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) {
++    struct userdata *u = userdata;
++    uint32_t idx;
++    pa_sink *sink_to_move;
++    pa_sink_input     *si;
++
++    pa_assert(c);
++    pa_assert(sink);
++    pa_assert(u);
++
++     /* There's no point in doing anything if the core is shut down anyway */
++    if (c->state == PA_CORE_SHUTDOWN)
++        return PA_HOOK_OK;
++
++    /* if unloading sink is not bt, just return */
++      if (!policy_is_bluez (sink)) {
++              pa_log_debug("[POLICY][%s] sink[%s][%d] unlinked but not a bluez....return\n", __func__,  sink->name, sink->index);
++              return PA_HOOK_OK;
++      }
++
++      pa_log_debug ("[POLICY][%s] SINK unlinked ================================ sink [%s][%d], bt_off_idx was [%d]",
++                      __func__, sink->name, sink->index,u->bt_off_idx);
++
++      u->bt_off_idx = sink->index;
++      pa_log_debug ("[POLICY][%s] bt_off_idx is set to [%d]", __func__, u->bt_off_idx);
++
++      /* BT sink is unloading, move sink-input to proper sink */
++      PA_IDXSET_FOREACH(si, c->sink_inputs, idx) {
++
++              if (!si->sink)
++                      continue;
++
++              /* Get role (if role is filter, skip it) */
++              if (policy_is_filter(si))
++                      continue;
++
++              /* Find who were using bt sink or bt related sink and move them to proper sink (alsa/mono_alsa) */
++              if (pa_streq (si->sink->name, SINK_MONO_BT) ||
++                      pa_streq (si->sink->name, SINK_MONO_COMBINED) ||
++                      pa_streq (si->sink->name, SINK_COMBINED) ||
++                      policy_is_bluez (si->sink)) {
++
++                      /* Move sink-input to proper sink : only alsa related sink is available now */
++                      sink_to_move = policy_get_sink_by_name (c, (u->is_mono)? SINK_MONO_ALSA : SINK_ALSA);
++                      if (sink_to_move) {
++                              pa_log_debug("[POLICY][%s] Moving sink-input[%d] from [%s] to [%s]", __func__, si->index, si->sink->name, sink_to_move->name);
++                              pa_sink_input_move_to(si, sink_to_move, false);
++                      } else {
++                              pa_log_warn("[POLICY][%s] No sink to move", __func__);
++                      }
++              }
++      }
++
++      pa_log_debug ("[POLICY][%s] unload sink in dependencies", __func__);
++
++    /* Unload mono_combine sink */
++    if (u->module_mono_combined) {
++        pa_module_unload(u->module->core, u->module_mono_combined, true);
++      u->module_mono_combined = NULL;
++    }
++
++      /* Unload combine sink */
++    if (u->module_combined) {
++        pa_module_unload(u->module->core, u->module_combined, true);
++        u->module_combined = NULL;
++    }
++
++    /* Unload mono_bt sink */
++      if (u->module_mono_bt) {
++              pa_module_unload(u->module->core, u->module_mono_bt, true);
++              u->module_mono_bt = NULL;
++      }
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t sink_unlink_post_hook_callback(pa_core *c, pa_sink *sink, void* userdata) {
++    struct userdata *u = userdata;
++
++    pa_assert(c);
++    pa_assert(sink);
++    pa_assert(u);
++
++    pa_log_debug("[POLICY][%s] SINK unlinked POST ================================ sink [%s][%d]", __func__, sink->name, sink->index);
++
++     /* There's no point in doing anything if the core is shut down anyway */
++    if (c->state == PA_CORE_SHUTDOWN)
++        return PA_HOOK_OK;
++
++    /* if unloading sink is not bt, just return */
++      if (!policy_is_bluez (sink)) {
++              pa_log_debug("[POLICY][%s] not a bluez....return\n", __func__);
++              return PA_HOOK_OK;
++      }
++
++    u->bt_off_idx = -1;
++    pa_log_debug ("[POLICY][%s] bt_off_idx is cleared to [%d]", __func__, u->bt_off_idx);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t sink_input_move_start_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
++    pa_core_assert_ref(core);
++    pa_sink_input_assert_ref(i);
++
++    /* There's no point in doing anything if the core is shut down anyway */
++   if (core->state == PA_CORE_SHUTDOWN)
++       return PA_HOOK_OK;
++
++    pa_log_debug ("[POLICY][%s]  sink_input_move_start_cb -------------------------------------- sink-input [%d] was sink [%s][%d] : Trying to mute!!!",
++              __func__, i->index, i->sink->name, i->sink->index);
++    pa_sink_input_set_mute(i, true, false);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
++    pa_core_assert_ref(core);
++    pa_sink_input_assert_ref(i);
++
++    /* There's no point in doing anything if the core is shut down anyway */
++   if (core->state == PA_CORE_SHUTDOWN)
++       return PA_HOOK_OK;
++
++    pa_log_debug("[POLICY][%s] sink_input_move_finish_cb -------------------------------------- sink-input [%d], sink [%s][%d], bt_off_idx [%d] : %s",
++              __func__, i->index, i->sink->name, i->sink->index, u->bt_off_idx,
++              (u->bt_off_idx == -1)? "Trying to un-mute!!!!" : "skip un-mute...");
++
++    /* If sink input move is caused by bt sink unlink, then skip un-mute operation */
++    if (u->bt_off_idx == -1) {
++        pa_sink_input_set_mute(i, false, false);
++    }
++
++    return PA_HOOK_OK;
++}
++
++static pa_source* policy_get_source_by_name (pa_core *c, const char* source_name)
++{
++      pa_source *s = NULL;
++      uint32_t idx;
++
++      if (c == NULL || source_name == NULL) {
++              pa_log_warn ("input param is null");
++              return NULL;
++      }
++
++      PA_IDXSET_FOREACH(s, c->sources, idx) {
++              if (pa_streq (s->name, source_name)) {
++                      pa_log_debug ("[POLICY][%s] return [%p] for [%s]\n",  __func__, s, source_name);
++                      return s;
++              }
++      }
++      return NULL;
++}
++
++/* Select source for given condition */
++static pa_source* policy_select_proper_source (pa_core *c, const char* policy)
++{
++      pa_source* source = NULL;
++      pa_source* def = NULL;
++
++      if (c == NULL || policy == NULL) {
++              pa_log_warn ("input param is null");
++              return NULL;
++      }
++
++      pa_assert (c);
++      def = pa_namereg_get_default_source(c);
++      if (def == NULL) {
++              pa_log_warn ("POLICY][%s] pa_namereg_get_default_source() returns null", __func__);
++              return NULL;
++      }
++
++      /* Select source  to */
++      if (pa_streq(policy, POLICY_VOIP)) {
++              source = policy_get_source_by_name (c, AEC_SOURCE);
++
++      } else {
++              source = def;
++      }
++
++      pa_log_debug ("[POLICY][%s] selected source : [%s]\n", __func__, (source)? source->name : "null");
++      return source;
++}
++
++
++/*  Called when new source-output is creating  */
++static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) {
++      const char *policy = NULL;
++      pa_assert(c);
++      pa_assert(new_data);
++      pa_assert(u);
++
++      if (!new_data->proplist) {
++              pa_log_debug("New stream lacks property data.");
++              return PA_HOOK_OK;
++      }
++
++      if (new_data->source) {
++              pa_log_debug("Not setting device for stream %s, because already set.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME)));
++              return PA_HOOK_OK;
++      }
++
++      /* If no policy exists, skip */
++      if (!(policy = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_POLICY))) {
++              pa_log_debug("[POLICY][%s] Not setting device for stream [%s], because it lacks policy.",
++                              __func__, pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME)));
++              return PA_HOOK_OK;
++      }
++      pa_log_debug("[POLICY][%s] Policy for stream [%s] = [%s]",
++                      __func__, pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME)), policy);
++
++      /* Set proper source to source-output */
++    pa_source* new_source = policy_select_proper_source(c, policy);
++    if(new_source != new_data->source)
++    {
++        pa_source_output_new_data_set_source(new_data, new_source, false);
++    }
++      /*new_data->save_source= false;
++      new_data->source= policy_select_proper_source (c, policy);*/
++      pa_log_debug("[POLICY][%s] set source of source-input to [%s]", __func__, (new_data->source)? new_data->source->name : "null");
++
++      return PA_HOOK_OK;
++}
++
++int pa__init(pa_module *m)
++{
++      pa_modargs *ma = NULL;
++      struct userdata *u;
++      bool on_hotplug = true, on_rescue = true;
++
++      pa_assert(m);
++
++      if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
++              pa_log("Failed to parse module arguments");
++              goto fail;
++      }
++
++      if (pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 ||
++              pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) {
++              pa_log("on_hotplug= and on_rescue= expect boolean arguments");
++              goto fail;
++      }
++
++      m->userdata = u = pa_xnew0(struct userdata, 1);
++      u->core = m->core;
++      u->module = m;
++      u->on_hotplug = on_hotplug;
++
++
++      /* A little bit later than module-stream-restore */
++      u->sink_input_new_hook_slot =
++                      pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+10, (pa_hook_cb_t) sink_input_new_hook_callback, u);
++
++      u->source_output_new_hook_slot =
++                      pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY+10, (pa_hook_cb_t) source_output_new_hook_callback, u);
++
++      if (on_hotplug) {
++              /* A little bit later than module-stream-restore */
++              u->sink_put_hook_slot =
++                      pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) sink_put_hook_callback, u);
++      }
++
++      /* sink unlink comes before sink-input unlink */
++      u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) sink_unlink_hook_callback, u);
++      u->sink_unlink_post_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], PA_HOOK_EARLY, (pa_hook_cb_t) sink_unlink_post_hook_callback, u);
++
++      u->sink_input_move_start_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_start_cb, u);
++      u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_finish_cb, u);
++
++      u->subscription = pa_subscription_new(u->core, PA_SUBSCRIPTION_MASK_SERVER, subscribe_cb, u);
++
++
++      u->bt_off_idx = -1;     /* initial bt off sink index */
++
++      u->module_mono_bt = NULL;
++      u->module_combined = NULL;
++      u->module_mono_combined = NULL;
++
++    u->protocol = pa_native_protocol_get(m->core);
++    pa_native_protocol_install_ext(u->protocol, m, extension_cb);
++
++    /* Get mono key value for init */
++      vconf_get_bool(MONO_KEY, &u->is_mono);
++
++      pa_log_info("policy module is loaded\n");
++
++      if (ma)
++              pa_modargs_free(ma);
++
++      return 0;
++
++fail:
++      if (ma)
++              pa_modargs_free(ma);
++
++      pa__done(m);
++
++      return -1;
++}
++
++void pa__done(pa_module *m)
++{
++    struct userdata* u;
++
++    pa_assert(m);
++
++    if (!(u = m->userdata))
++        return;
++
++    if (u->sink_input_new_hook_slot)
++        pa_hook_slot_free(u->sink_input_new_hook_slot);
++    if (u->sink_put_hook_slot)
++        pa_hook_slot_free(u->sink_put_hook_slot);
++    if (u->sink_unlink_slot)
++        pa_hook_slot_free(u->sink_unlink_slot);
++    if (u->sink_unlink_post_slot)
++        pa_hook_slot_free(u->sink_unlink_post_slot);
++    if (u->sink_input_move_start_slot)
++        pa_hook_slot_free(u->sink_input_move_start_slot);
++    if (u->sink_input_move_finish_slot)
++        pa_hook_slot_free(u->sink_input_move_finish_slot);
++    if (u->subscription)
++        pa_subscription_free(u->subscription);
++    if (u->protocol) {
++        pa_native_protocol_remove_ext(u->protocol, m);
++        pa_native_protocol_unref(u->protocol);
++    }
++    if (u->source_output_new_hook_slot)
++        pa_hook_slot_free(u->source_output_new_hook_slot);
++
++    pa_xfree(u);
++
++
++      pa_log_info("policy module is unloaded\n");
++}
+diff --git a/src/pulse/ext-policy.c b/src/pulse/ext-policy.c
+new file mode 100644
+index 0000000..f3a3a8c
+--- /dev/null
++++ b/src/pulse/ext-policy.c
+@@ -0,0 +1,177 @@
++/***
++  This file is part of PulseAudio.
++
++  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 <pulse/context.h>
++#include <pulse/gccmacro.h>
++#include <pulse/xmalloc.h>
++
++#include <pulsecore/macro.h>
++#include <pulsecore/pstream-util.h>
++
++#include "internal.h"
++#include "operation.h"
++#include "fork-detect.h"
++
++#include "ext-policy.h"
++
++enum {
++    SUBCOMMAND_TEST,
++    SUBCOMMAND_MONO,
++    SUBCOMMAND_BALANCE,
++};
++
++static void ext_policy_test_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
++    pa_operation *o = userdata;
++    uint32_t version = PA_INVALID_INDEX;
++
++    pa_assert(pd);
++    pa_assert(o);
++    pa_assert(PA_REFCNT_VALUE(o) >= 1);
++
++    if (!o->context)
++        goto finish;
++
++    if (command != PA_COMMAND_REPLY) {
++        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
++            goto finish;
++
++    } else if (pa_tagstruct_getu32(t, &version) < 0 ||
++               !pa_tagstruct_eof(t)) {
++
++        pa_context_fail(o->context, PA_ERR_PROTOCOL);
++        goto finish;
++    }
++
++    if (o->callback) {
++        pa_ext_policy_test_cb_t cb = (pa_ext_policy_test_cb_t) o->callback;
++        cb(o->context, version, o->userdata);
++    }
++
++finish:
++    pa_operation_done(o);
++    pa_operation_unref(o);
++}
++
++pa_operation *pa_ext_policy_test(
++        pa_context *c,
++        pa_ext_device_manager_test_cb_t cb,
++        void *userdata) {
++
++    uint32_t tag;
++    pa_operation *o;
++    pa_tagstruct *t;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED);
++
++    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++    t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag);
++    pa_tagstruct_putu32(t, PA_INVALID_INDEX);
++    pa_tagstruct_puts(t, "module-policy");
++    pa_tagstruct_putu32(t, SUBCOMMAND_TEST);
++    pa_pstream_send_tagstruct(c->pstream, t);
++    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_policy_test_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++    return o;
++}
++
++pa_operation *pa_ext_policy_set_mono (
++        pa_context *c,
++        int enable,
++        pa_context_success_cb_t cb,
++        void *userdata) {
++
++    uint32_t tag;
++    pa_operation *o = NULL;
++    pa_tagstruct *t = NULL;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED);
++
++    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++    t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag);
++    pa_tagstruct_putu32(t, PA_INVALID_INDEX);
++    pa_tagstruct_puts(t, "module-policy");
++    pa_tagstruct_putu32(t, SUBCOMMAND_MONO);
++    pa_tagstruct_put_boolean(t, !!enable);
++
++    pa_pstream_send_tagstruct(c->pstream, t);
++    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++    return o;
++}
++
++pa_operation *pa_ext_policy_set_balance (
++        pa_context *c,
++        double *balance,
++        pa_context_success_cb_t cb,
++        void *userdata) {
++
++    uint32_t tag;
++    pa_operation *o = NULL;
++    pa_tagstruct *t = NULL;
++    pa_cvolume cvol;
++    pa_channel_map map;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED);
++
++    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++    t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag);
++    pa_tagstruct_putu32(t, PA_INVALID_INDEX);
++    pa_tagstruct_puts(t, "module-policy");
++    pa_tagstruct_putu32(t, SUBCOMMAND_BALANCE);
++
++    /* Prepare cvolume for transfer */
++    pa_channel_map_init_stereo(&map);
++    pa_cvolume_set(&cvol, map.channels, 65535);
++
++    pa_log_error ("balance = %f", *balance);
++
++    pa_cvolume_set_balance(&cvol, &map, *balance);
++
++    pa_log_error ("balance get = %f", pa_cvolume_get_balance(&cvol, &map));
++
++    pa_tagstruct_put_cvolume(t, &cvol);
++
++    pa_pstream_send_tagstruct(c->pstream, t);
++    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++    return o;
++}
++
+diff --git a/src/pulse/ext-policy.h b/src/pulse/ext-policy.h
+new file mode 100644
+index 0000000..ec62ead
+--- /dev/null
++++ b/src/pulse/ext-policy.h
+@@ -0,0 +1,61 @@
++#ifndef foopulseextpolicyhfoo
++#define foopulseextpolicyhfoo
++
++/***
++  This file is part of PulseAudio.
++
++  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/context.h>
++#include <pulse/version.h>
++
++/** \file
++ *
++ * Routines for controlling module-policy
++ */
++
++PA_C_DECL_BEGIN
++
++/** Callback prototype for pa_ext_policy_test(). \since 0.9.21 */
++typedef void (*pa_ext_policy_test_cb_t)(
++        pa_context *c,
++        uint32_t version,
++        void *userdata);
++
++/** Test if this extension module is available in the server. \since 0.9.21 */
++pa_operation *pa_ext_policy_test(
++        pa_context *c,
++        pa_ext_policy_test_cb_t cb,
++        void *userdata);
++
++/** Enable the mono mode. \since 0.9.21 */
++pa_operation *pa_ext_policy_set_mono (
++        pa_context *c,
++        int enable,
++        pa_context_success_cb_t cb,
++        void *userdata);
++
++/** Enable the balance mode. \since 0.9.21 */
++pa_operation *pa_ext_policy_set_balance (
++        pa_context *c,
++        double *balance,
++        pa_context_success_cb_t cb,
++        void *userdata);
++
++PA_C_DECL_END
++
++#endif
+diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h
+index dc3cddc..341abaa 100644
+--- a/src/pulse/proplist.h
++++ b/src/pulse/proplist.h
+@@ -65,6 +65,9 @@ PA_C_DECL_BEGIN
+ /** For streams: logic role of this media. One of the strings "video", "music", "game", "event", "phone", "animation", "production", "a11y", "test" */
+ #define PA_PROP_MEDIA_ROLE                     "media.role"
++/** For streams: logic role of this media. One of the strings "auto", "phone" */
++#define PA_PROP_MEDIA_POLICY                "media.policy"
++
+ /** For streams: the name of a filter that is desired, e.g.\ "echo-cancel" or "equalizer-sink". PulseAudio may choose to not apply the filter if it does not make sense (for example, applying echo-cancellation on a Bluetooth headset probably does not make sense. \since 1.0 */
+ #define PA_PROP_FILTER_WANT                    "filter.want"
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0039-add-bluetooth-a2dp-aptx-codec-support-samsung.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0039-add-bluetooth-a2dp-aptx-codec-support-samsung.patch
new file mode 100644 (file)
index 0000000..1355048
--- /dev/null
@@ -0,0 +1,1024 @@
+From: "vivian,zhang" <vivian.zhang@intel.com>
+Date: Tue, 18 Jun 2013 16:23:45 +0800
+Subject: add bluetooth a2dp aptx codec support - samsung
+
+Change-Id: I2c90198774c1e7d3e2ecb99f2dd365d56308f157
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ configure.ac                                      |  20 ++
+ src/Makefile.am                                   |  27 +++
+ src/modules/bluetooth/a2dp-codecs.h               |  39 ++++
+ src/modules/bluetooth/bluetooth-util.h            | 183 +++++++++++++++++
+ src/modules/bluetooth/bluez4-util.c               | 184 ++++++++++++++++-
+ src/modules/bluetooth/module-bluetooth-discover.c |  37 ++++
+ src/modules/bluetooth/module-bluez4-device.c      | 236 ++++++++++++++++++++++
+ 7 files changed, 724 insertions(+), 2 deletions(-)
+ create mode 100644 src/modules/bluetooth/bluetooth-util.h
+
+diff --git a/configure.ac b/configure.ac
+index 0e205d3..ebff16c 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -1079,6 +1079,26 @@ else
+ fi
+ AC_SUBST(BLUETOOTH_HEADSET_BACKEND)
++#### Bluetooth A2DP aptx codec support(optional) ####
++AC_ARG_ENABLE([bt_a2dp_aptx],
++    AS_HELP_STRING([--enable-bt-a2dp-aptx],[Enable optional Bluetooth A2DP aptx codec support(arm only)]),
++        [
++            case "${enableval}" in
++                yes) bt_a2dp_aptx=yes ;;
++                no) bt_a2dp_aptx=no ;;
++                *) AC_MSG_ERROR(bad value ${enableval} for --enable-bt-a2dp-aptx) ;;
++            esac
++        ],
++        [bt_a2dp_aptx=false])
++if test "x${bt_a2dp_aptx}" == xyes ; then
++      HAVE_BT_A2DP_APTX=1
++else
++      HAVE_BT_A2DP_APTX=0
++fi
++
++AC_SUBST(HAVE_BT_A2DP_APTX)
++AM_CONDITIONAL([HAVE_BT_A2DP_APTX], [test "x$HAVE_BT_A2DP_APTX" = x1])
++
+ #### UDEV support (optional) ####
+ AC_ARG_ENABLE([udev],
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 4872dfd..3c062eb 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -2078,7 +2078,11 @@ module_bluetooth_policy_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+ module_bluetooth_discover_la_SOURCES = modules/bluetooth/module-bluetooth-discover.c
+ module_bluetooth_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
+ module_bluetooth_discover_la_LIBADD = $(MODULE_LIBADD)
++if HAVE_BT_A2DP_APTX
++module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS) -DBLUETOOTH_APTX_SUPPORT
++else
+ module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS)
++endif
+ # Bluetooth BlueZ 4 sink / source
+ module_bluez4_discover_la_SOURCES = modules/bluetooth/module-bluez4-discover.c
+@@ -2092,12 +2096,23 @@ libbluez4_util_la_SOURCES = \
+               modules/bluetooth/bluez4-util.h
+ libbluez4_util_la_LDFLAGS = -avoid-version
+ libbluez4_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
++
++if HAVE_BT_A2DP_APTX
++libbluez4_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -DBLUETOOTH_APTX_SUPPORT
++else
+ libbluez4_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
++endif
+ module_bluez4_device_la_SOURCES = modules/bluetooth/module-bluez4-device.c modules/bluetooth/rtp.h
+ module_bluez4_device_la_LDFLAGS = $(MODULE_LDFLAGS)
+ module_bluez4_device_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS) libbluez4-util.la
++
++if HAVE_BT_A2DP_APTX
++module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS) \
++                                    -DBLUETOOTH_APTX_SUPPORT
++else
+ module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
++endif
+ # Bluetooth BlueZ 5 sink / source
+ libbluez5_util_la_SOURCES = \
+@@ -2108,7 +2123,12 @@ libbluez5_util_la_SOURCES = \
+               modules/bluetooth/hfaudioagent-@BLUETOOTH_HEADSET_BACKEND@.c
+ libbluez5_util_la_LDFLAGS = -avoid-version
+ libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
++
++if HAVE_BT_A2DP_APTX
++libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -DBLUETOOTH_APTX_SUPPORT
++else
+ libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
++endif
+ module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c
+ module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
+@@ -2120,6 +2140,13 @@ module_bluez5_device_la_LDFLAGS = $(MODULE_LDFLAGS)
+ module_bluez5_device_la_LIBADD = $(MODULE_LIBADD) $(SBC_LIBS) libbluez5-util.la
+ module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) $(SBC_CFLAGS)
++if HAVE_BT_A2DP_APTX
++module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) $(SBC_CFLAGS) \
++                                    -DBLUETOOTH_APTX_SUPPORT
++else
++module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) $(SBC_CFLAGS)
++endif
++
+ # Apple Airtunes/RAOP
+ module_raop_sink_la_SOURCES = modules/raop/module-raop-sink.c
+ module_raop_sink_la_LDFLAGS = $(MODULE_LDFLAGS)
+diff --git a/src/modules/bluetooth/a2dp-codecs.h b/src/modules/bluetooth/a2dp-codecs.h
+index 51c796a..c94812b 100644
+--- a/src/modules/bluetooth/a2dp-codecs.h
++++ b/src/modules/bluetooth/a2dp-codecs.h
+@@ -27,6 +27,7 @@
+ #define A2DP_CODEC_MPEG24             0x02
+ #define A2DP_CODEC_ATRAC              0x03
++#define A2DP_CODEC_NON_A2DP           0xFF
+ #define SBC_SAMPLING_FREQ_16000               (1 << 3)
+ #define SBC_SAMPLING_FREQ_32000               (1 << 2)
+ #define SBC_SAMPLING_FREQ_44100               (1 << 1)
+@@ -67,6 +68,32 @@
+ #define MAX_BITPOOL 64
+ #define MIN_BITPOOL 2
++/*#define APTX_CHANNEL_MODE_STEREO                    2 */
++/*
++ * aptX codec for Bluetooth only supports stereo mode with value 2
++ * But we do have sink devices programmed to send capabilities with other channel mode support.
++ * So to handle the case and keeping codec symmetry with SBC etc., we do define other channel mode,
++ * and we always make sure to set configuration with APTX_CHANNEL_MODE_STEREO only.
++ *
++ * */
++
++#define APTX_CHANNEL_MODE_MONO                        (1 << 3)
++#define APTX_CHANNEL_MODE_DUAL_CHANNEL        (1 << 2)
++#define APTX_CHANNEL_MODE_STEREO              (1 << 1)
++#define APTX_CHANNEL_MODE_JOINT_STEREO        1
++
++#define APTX_VENDOR_ID0               0x4F /*APTX codec ID 79*/
++#define APTX_VENDOR_ID1               0x0
++#define APTX_VENDOR_ID2               0x0
++#define APTX_VENDOR_ID3               0x0
++
++#define APTX_CODEC_ID0                0x1
++#define APTX_CODEC_ID1                0x0
++
++#define APTX_SAMPLING_FREQ_16000      (1 << 3)
++#define APTX_SAMPLING_FREQ_32000      (1 << 2)
++#define APTX_SAMPLING_FREQ_44100      (1 << 1)
++#define APTX_SAMPLING_FREQ_48000      1
+ #if __BYTE_ORDER == __LITTLE_ENDIAN
+ typedef struct {
+@@ -89,6 +116,12 @@ typedef struct {
+       uint16_t bitrate;
+ } __attribute__ ((packed)) a2dp_mpeg_t;
++typedef struct {
++      uint8_t vendor_id[4];
++      uint8_t codec_id[2];
++      uint8_t channel_mode:4;
++      uint8_t frequency:4;
++} __attribute__ ((packed)) a2dp_aptx_t;
+ #elif __BYTE_ORDER == __BIG_ENDIAN
+ typedef struct {
+@@ -110,6 +143,12 @@ typedef struct {
+       uint8_t frequency:6;
+       uint16_t bitrate;
+ } __attribute__ ((packed)) a2dp_mpeg_t;
++typedef struct {
++      uint8_t vendor_id[4];
++      uint8_t codec_id[2];
++      uint8_t frequency:4;
++      uint8_t channel_mode:4;
++} __attribute__ ((packed)) a2dp_aptx_t;
+ #else
+ #error "Unknown byte order"
+diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h
+new file mode 100644
+index 0000000..859ad2d
+--- /dev/null
++++ b/src/modules/bluetooth/bluetooth-util.h
+@@ -0,0 +1,183 @@
++#ifndef foobluetoothutilhfoo
++#define foobluetoothutilhfoo
++
++/***
++  This file is part of PulseAudio.
++
++  Copyright 2008-2009 Joao Paulo Rechi Vita
++
++  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 <dbus/dbus.h>
++
++#include <pulsecore/llist.h>
++#include <pulsecore/macro.h>
++
++#define PA_BLUETOOTH_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
++
++/* UUID copied from bluez/audio/device.h */
++#define GENERIC_AUDIO_UUID      "00001203-0000-1000-8000-00805f9b34fb"
++
++#define HSP_HS_UUID             "00001108-0000-1000-8000-00805f9b34fb"
++#define HSP_AG_UUID             "00001112-0000-1000-8000-00805f9b34fb"
++
++#define HFP_HS_UUID             "0000111e-0000-1000-8000-00805f9b34fb"
++#define HFP_AG_UUID             "0000111f-0000-1000-8000-00805f9b34fb"
++
++#define ADVANCED_AUDIO_UUID     "0000110d-0000-1000-8000-00805f9b34fb"
++
++#define A2DP_SOURCE_UUID        "0000110a-0000-1000-8000-00805f9b34fb"
++#define A2DP_SINK_UUID          "0000110b-0000-1000-8000-00805f9b34fb"
++
++#define HSP_MAX_GAIN 15
++
++typedef struct pa_bluetooth_uuid pa_bluetooth_uuid;
++typedef struct pa_bluetooth_device pa_bluetooth_device;
++typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
++typedef struct pa_bluetooth_transport pa_bluetooth_transport;
++
++struct userdata;
++
++struct pa_bluetooth_uuid {
++    char *uuid;
++    PA_LLIST_FIELDS(pa_bluetooth_uuid);
++};
++
++enum profile {
++    PROFILE_A2DP,
++    PROFILE_A2DP_SOURCE,
++    PROFILE_HSP,
++    PROFILE_HFGW,
++    PROFILE_OFF
++};
++
++#define PA_BLUETOOTH_PROFILE_COUNT PROFILE_OFF
++
++struct pa_bluetooth_hook_uuid_data {
++    pa_bluetooth_device *device;
++    const char *uuid;
++};
++
++/* Hook data: pa_bluetooth_discovery pointer. */
++typedef enum pa_bluetooth_hook {
++    PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */
++    PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED, /* Call data: pa_bluetooth_hook_uuid_data */
++    PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */
++    PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED, /* Call data: pa_bluetooth_transport */
++    PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
++    PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
++    PA_BLUETOOTH_HOOK_MAX
++} pa_bluetooth_hook_t;
++
++typedef enum pa_bluetooth_transport_state {
++    PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED,
++    PA_BLUETOOTH_TRANSPORT_STATE_IDLE, /* Connected but not playing */
++    PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
++} pa_bluetooth_transport_state_t;
++
++struct pa_bluetooth_transport {
++    pa_bluetooth_device *device;
++    char *owner;
++    char *path;
++    enum profile profile;
++    uint8_t codec;
++    uint8_t *config;
++    int config_size;
++
++    pa_bluetooth_transport_state_t state;
++    bool nrec;
++    uint16_t microphone_gain; /* Used for HSP/HFP */
++    uint16_t speaker_gain; /* Used for HSP/HFP */
++};
++
++/* This enum is shared among Audio, Headset, AudioSink, and AudioSource, although not all values are acceptable in all profiles */
++typedef enum pa_bt_audio_state {
++    PA_BT_AUDIO_STATE_INVALID = -1,
++    PA_BT_AUDIO_STATE_DISCONNECTED,
++    PA_BT_AUDIO_STATE_CONNECTING,
++    PA_BT_AUDIO_STATE_CONNECTED,
++    PA_BT_AUDIO_STATE_PLAYING
++} pa_bt_audio_state_t;
++
++struct pa_bluetooth_device {
++    pa_bluetooth_discovery *discovery;
++    bool dead;
++
++    int device_info_valid;      /* 0: no results yet; 1: good results; -1: bad results ... */
++
++    /* Device information */
++    char *name;
++    char *path;
++    pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
++    int paired;
++    char *alias;
++    PA_LLIST_HEAD(pa_bluetooth_uuid, uuids);
++    char *address;
++    int class;
++    int trusted;
++
++    /* Audio state */
++    pa_bt_audio_state_t audio_state;
++
++    /* AudioSink, AudioSource, Headset and HandsfreeGateway states */
++    pa_bt_audio_state_t profile_state[PA_BLUETOOTH_PROFILE_COUNT];
++};
++
++pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core);
++pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y);
++void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *d);
++
++pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *d, const char* path);
++pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *d, const char* address);
++
++bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d);
++
++int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu);
++void pa_bluetooth_transport_release(pa_bluetooth_transport *t);
++
++void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value);
++void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value);
++
++pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
++
++typedef enum pa_bt_form_factor {
++    PA_BT_FORM_FACTOR_UNKNOWN,
++    PA_BT_FORM_FACTOR_HEADSET,
++    PA_BT_FORM_FACTOR_HANDSFREE,
++    PA_BT_FORM_FACTOR_MICROPHONE,
++    PA_BT_FORM_FACTOR_SPEAKER,
++    PA_BT_FORM_FACTOR_HEADPHONE,
++    PA_BT_FORM_FACTOR_PORTABLE,
++    PA_BT_FORM_FACTOR_CAR,
++    PA_BT_FORM_FACTOR_HIFI,
++    PA_BT_FORM_FACTOR_PHONE,
++} pa_bt_form_factor_t;
++
++pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class);
++const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff);
++
++char *pa_bluetooth_cleanup_name(const char *name);
++
++bool pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid);
++const char *pa_bt_profile_to_string(enum profile profile);
++
++#ifdef BLUETOOTH_APTX_SUPPORT
++int pa_load_aptx(const char *aptx_lib_name);
++int pa_unload_aptx(void);
++void* pa_aptx_get_handle(void);
++#endif
++#endif
+diff --git a/src/modules/bluetooth/bluez4-util.c b/src/modules/bluetooth/bluez4-util.c
+index f047b73..7e7aed0 100644
+--- a/src/modules/bluetooth/bluez4-util.c
++++ b/src/modules/bluetooth/bluez4-util.c
+@@ -22,6 +22,9 @@
+ #ifdef HAVE_CONFIG_H
+ #include <config.h>
+ #endif
++#ifdef BLUETOOTH_APTX_SUPPORT
++#include <dlfcn.h>
++#endif
+ #include <pulse/xmalloc.h>
+@@ -36,6 +39,9 @@
+ #define ENDPOINT_PATH_HFP_HS "/MediaEndpoint/BlueZ4/HFPHS"
+ #define ENDPOINT_PATH_A2DP_SOURCE "/MediaEndpoint/BlueZ4/A2DPSource"
+ #define ENDPOINT_PATH_A2DP_SINK "/MediaEndpoint/BlueZ4/A2DPSink"
++#ifdef BLUETOOTH_APTX_SUPPORT
++#define ENDPOINT_PATH_A2DP_APTX_SOURCE "/MediaEndpoint/Bluez4/A2DPSource_aptx"
++#endif
+ #define ENDPOINT_INTROSPECT_XML                                         \
+     DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
+@@ -80,6 +86,56 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluez4_discovery *y, DBusMess
+ static void found_adapter(pa_bluez4_discovery *y, const char *path);
+ static pa_bluez4_device *found_device(pa_bluez4_discovery *y, const char* path);
++#ifdef BLUETOOTH_APTX_SUPPORT
++static void *aptx_handle = NULL;
++
++int pa_unload_aptx(void)
++{
++      if (aptx_handle == NULL) {
++              pa_log_warn("Unable to unload apt-X library");
++              return -1;
++      }
++
++      dlclose(aptx_handle);
++      aptx_handle = NULL;
++
++      pa_log_debug("unloaded apt-X library successfully");
++      return 0;
++}
++
++int pa_load_aptx(const char *aptx_lib_name)
++{
++      char* lib_path = NULL ;
++
++        if(aptx_lib_name == NULL)
++              return -1;
++
++        lib_path = pa_sprintf_malloc("%s/%s", PA_DLSEARCHPATH, aptx_lib_name);
++
++      if (!lib_path)
++              return -1;
++
++      pa_log_info("aptx_lib_path = [%s]", lib_path);
++
++      aptx_handle = dlopen(lib_path, RTLD_LAZY);
++      if (aptx_handle == NULL) {
++              pa_log_warn("Unable to load apt-X library [%s]", dlerror());
++              pa_xfree(lib_path);
++              return -1;
++      }
++
++      pa_log_debug("loaded apt-X library successfully");
++      pa_xfree(lib_path);
++
++      return 0;
++}
++
++void* pa_aptx_get_handle(void)
++{
++      return aptx_handle;
++}
++#endif
++
+ static pa_bluez4_audio_state_t audio_state_from_string(const char* value) {
+     pa_assert(value);
+@@ -835,6 +891,8 @@ static void register_endpoint(pa_bluez4_discovery *y, const char *path, const ch
+         uint8_t capability = 0;
+         pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capability, 1);
+     } else {
++        pa_log_debug("register_endpoint: codec=%d[%s]", codec, codec==A2DP_CODEC_SBC ? "A2DP_CODEC_SBC" : codec==A2DP_CODEC_NON_A2DP ? "A2DP_CODEC_NON_A2DP" : "unknown");
++        if (codec == A2DP_CODEC_SBC) {
+         a2dp_sbc_t capabilities;
+         capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL |
+@@ -849,6 +907,23 @@ static void register_endpoint(pa_bluez4_discovery *y, const char *path, const ch
+         capabilities.max_bitpool = MAX_BITPOOL;
+         pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
++        } else if (codec == A2DP_CODEC_NON_A2DP ) {
++          /* aptx */
++            a2dp_aptx_t capabilities;
++
++            capabilities.vendor_id[0] = APTX_VENDOR_ID0;
++            capabilities.vendor_id[1] = APTX_VENDOR_ID1;
++            capabilities.vendor_id[2] = APTX_VENDOR_ID2;
++            capabilities.vendor_id[3] = APTX_VENDOR_ID3;
++
++            capabilities.codec_id[0] = APTX_CODEC_ID0;
++            capabilities.codec_id[1] = APTX_CODEC_ID1;
++
++            capabilities.channel_mode= APTX_CHANNEL_MODE_STEREO;
++            capabilities.frequency= APTX_SAMPLING_FREQ_44100;
++
++            pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
++        }
+     }
+     dbus_message_iter_close_container(&i, &d);
+@@ -857,6 +932,7 @@ static void register_endpoint(pa_bluez4_discovery *y, const char *path, const ch
+ }
+ static void found_adapter(pa_bluez4_discovery *y, const char *path) {
++
+     DBusMessage *m;
+     pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "GetProperties"));
+@@ -866,6 +942,10 @@ static void found_adapter(pa_bluez4_discovery *y, const char *path) {
+     register_endpoint(y, path, ENDPOINT_PATH_HFP_HS, HFP_HS_UUID);
+     register_endpoint(y, path, ENDPOINT_PATH_A2DP_SOURCE, A2DP_SOURCE_UUID);
+     register_endpoint(y, path, ENDPOINT_PATH_A2DP_SINK, A2DP_SINK_UUID);
++#ifdef BLUETOOTH_APTX_SUPPORT
++    if (aptx_handle)
++        register_endpoint(y, path, ENDPOINT_PATH_A2DP_APTX_SOURCE, A2DP_SOURCE_UUID);
++#endif
+ }
+ static void list_adapters(pa_bluez4_discovery *y) {
+@@ -1349,7 +1429,11 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
+         p = PA_BLUEZ4_PROFILE_HSP;
+     else if (dbus_message_has_path(m, ENDPOINT_PATH_HFP_HS))
+         p = PA_BLUEZ4_PROFILE_HFGW;
++#ifdef BLUETOOTH_APTX_SUPPORT
++    else if (dbus_message_has_path(m, ENDPOINT_PATH_A2DP_SOURCE) || dbus_message_has_path(m, ENDPOINT_PATH_A2DP_APTX_SOURCE))
++#else
+     else if (dbus_message_has_path(m, ENDPOINT_PATH_A2DP_SOURCE))
++#endif
+         p = PA_BLUEZ4_PROFILE_A2DP;
+     else
+         p = PA_BLUEZ4_PROFILE_A2DP_SOURCE;
+@@ -1475,6 +1559,84 @@ static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
+     }
+ }
++#ifdef BLUETOOTH_APTX_SUPPORT
++static DBusMessage *endpoint_select_configuration_for_aptx(DBusConnection *c, DBusMessage *m, void *userdata) {
++    a2dp_aptx_t *cap;
++    a2dp_aptx_t config;
++    uint8_t *pconf = (uint8_t *) &config;
++    int size;
++    DBusMessage *r;
++    DBusError e;
++
++    dbus_error_init(&e);
++
++    if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
++        pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message);
++        dbus_error_free(&e);
++        goto fail;
++    }
++
++    pa_assert(size == sizeof(config));
++
++    memset(&config, 0, sizeof(config));
++
++    if (cap->vendor_id[0] == APTX_VENDOR_ID0 &&
++        cap->vendor_id[1] == APTX_VENDOR_ID1 &&
++        cap->vendor_id[2] == APTX_VENDOR_ID2 &&
++        cap->vendor_id[3] == APTX_VENDOR_ID3 &&
++         cap->codec_id[0] == APTX_CODEC_ID0  &&
++         cap->codec_id[1] == APTX_CODEC_ID1  )
++        pa_log_debug("A2DP_CODEC_NON_A2DP and this is APTX Codec");
++    else {
++        pa_log_debug("A2DP_CODEC_NON_A2DP but this is not APTX Codec");
++        goto fail;
++    }
++
++    memcpy(&config,cap, sizeof(config));
++
++/* The below code shuld be re-written by aptx */
++/* And we should configure pulseaudio freq */
++
++    if (cap->frequency & APTX_SAMPLING_FREQ_44100)
++        config.frequency = APTX_SAMPLING_FREQ_44100;
++    else if (cap->frequency & APTX_SAMPLING_FREQ_48000)
++        config.frequency = APTX_SAMPLING_FREQ_48000;
++    else if (cap->frequency & APTX_SAMPLING_FREQ_32000)
++        config.frequency = APTX_SAMPLING_FREQ_32000;
++    else if (cap->frequency & APTX_SAMPLING_FREQ_16000)
++        config.frequency = APTX_SAMPLING_FREQ_16000;
++    else {
++        pa_log_error("No aptx supported frequencies");
++        goto fail;
++    }
++
++    if (cap->channel_mode & APTX_CHANNEL_MODE_JOINT_STEREO)
++        config.channel_mode = APTX_CHANNEL_MODE_STEREO;
++    else if (cap->channel_mode & APTX_CHANNEL_MODE_STEREO)
++        config.channel_mode = APTX_CHANNEL_MODE_STEREO;
++    else if (cap->channel_mode & APTX_CHANNEL_MODE_DUAL_CHANNEL)
++        config.channel_mode = APTX_CHANNEL_MODE_STEREO;
++    else {
++        pa_log_error("No aptx supported channel modes");
++        goto fail;
++    }
++
++    pa_assert_se(r = dbus_message_new_method_return(m));
++
++    pa_assert_se(dbus_message_append_args(
++                                     r,
++                                     DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size,
++                                     DBUS_TYPE_INVALID));
++
++    return r;
++
++fail:
++    pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
++                                                        "Unable to select configuration")));
++    return r;
++}
++#endif
++
+ static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
+     pa_bluez4_discovery *y = userdata;
+     a2dp_sbc_t *cap, config;
+@@ -1493,6 +1655,10 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage
+         { 48000U, SBC_SAMPLING_FREQ_48000 }
+     };
++#ifdef BLUETOOTH_APTX_SUPPORT
++    if (dbus_message_has_path(m, A2DP_APTX_SOURCE_ENDPOINT))
++        return endpoint_select_configuration_for_aptx(c ,m ,userdata);
++#endif
+     dbus_error_init(&e);
+     if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
+@@ -1614,8 +1780,13 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
+     dbus_error_init(&e);
+-    if (!pa_streq(path, ENDPOINT_PATH_A2DP_SOURCE) && !pa_streq(path, ENDPOINT_PATH_A2DP_SINK)
+-            && !pa_streq(path, ENDPOINT_PATH_HFP_AG) && !pa_streq(path, ENDPOINT_PATH_HFP_HS))
++    if (!pa_streq(path, ENDPOINT_PATH_A2DP_SOURCE) &&
++        !pa_streq(path, ENDPOINT_PATH_A2DP_SINK) &&
++        !pa_streq(path, ENDPOINT_PATH_HFP_AG) &&
++#ifdef BLUETOOTH_APTX_SUPPORT
++        !pa_streq(path, ENDPOINT_PATH_A2DP_APTX_SOURCE) &&
++#endif
++        !pa_streq(path, ENDPOINT_PATH_HFP_HS))
+         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+     if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+@@ -1706,6 +1877,11 @@ pa_bluez4_discovery* pa_bluez4_discovery_get(pa_core *c) {
+     pa_assert_se(dbus_connection_register_object_path(conn, ENDPOINT_PATH_A2DP_SOURCE, &vtable_endpoint, y));
+     pa_assert_se(dbus_connection_register_object_path(conn, ENDPOINT_PATH_A2DP_SINK, &vtable_endpoint, y));
++#ifdef BLUETOOTH_APTX_SUPPORT
++    if (aptx_handle)
++        pa_assert_se(dbus_connection_register_object_path(conn, ENDPOINT_PATH_A2DP_APTX_SOURCE, &vtable_endpoint, y));
++#endif
++
+     list_adapters(y);
+     return y;
+@@ -1754,6 +1930,10 @@ void pa_bluez4_discovery_unref(pa_bluez4_discovery *y) {
+         dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), ENDPOINT_PATH_HFP_HS);
+         dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), ENDPOINT_PATH_A2DP_SOURCE);
+         dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), ENDPOINT_PATH_A2DP_SINK);
++#ifdef BLUETOOTH_APTX_SUPPORT
++        if (aptx_handle)
++            dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), ENDPOINT_PATH_A2DP_APTX_SOURCE);
++#endif
+         pa_dbus_remove_matches(
+             pa_dbus_connection_get(y->connection),
+             "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
+diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c
+index 0bcfcf9..e1dbec5 100644
+--- a/src/modules/bluetooth/module-bluetooth-discover.c
++++ b/src/modules/bluetooth/module-bluetooth-discover.c
+@@ -34,6 +34,17 @@ PA_MODULE_DESCRIPTION("Detect available Bluetooth daemon and load the correspond
+ PA_MODULE_VERSION(PACKAGE_VERSION);
+ PA_MODULE_LOAD_ONCE(true);
++#ifdef BLUETOOTH_APTX_SUPPORT
++PA_MODULE_USAGE("aptx_lib_name=<name of aptx library name>");
++#endif
++
++#ifdef BLUETOOTH_APTX_SUPPORT
++static const char* const valid_modargs[] = {
++    "aptx_lib_name",
++    NULL
++};
++#endif
++
+ struct userdata {
+     uint32_t bluez5_module_idx;
+     uint32_t bluez4_module_idx;
+@@ -43,8 +54,30 @@ int pa__init(pa_module* m) {
+     struct userdata *u;
+     pa_module *mm;
++#ifdef BLUETOOTH_APTX_SUPPORT
++    pa_modargs *ma = NULL;
++    const char *aptx_lib_name = NULL;
++#endif
++
+     pa_assert(m);
++#ifdef BLUETOOTH_APTX_SUPPORT
++    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
++        pa_log("Failed to parse module arguments");
++        goto fail;
++    }
++
++    if (pa_modargs_get_value(ma, "async", NULL))
++        pa_log_warn("The 'async' argument is deprecated and does nothing.");
++
++
++    aptx_lib_name = pa_modargs_get_value(ma, "aptx_lib_name", NULL);
++    if (aptx_lib_name)
++        pa_load_aptx(aptx_lib_name);
++    else
++        pa_log("Failed to parse aptx_lib_name argument.");
++#endif
++
+     m->userdata = u = pa_xnew0(struct userdata, 1);
+     u->bluez5_module_idx = PA_INVALID_INDEX;
+     u->bluez4_module_idx = PA_INVALID_INDEX;
+@@ -83,5 +116,9 @@ void pa__done(pa_module* m) {
+     if (u->bluez4_module_idx != PA_INVALID_INDEX)
+         pa_module_unload_by_index(m->core, u->bluez4_module_idx, true);
++#ifdef BLUETOOTH_APTX_SUPPORT
++    pa_unload_aptx();
++#endif
++
+     pa_xfree(u);
+ }
+diff --git a/src/modules/bluetooth/module-bluez4-device.c b/src/modules/bluetooth/module-bluez4-device.c
+index b0b12f8..eba92c6 100644
+--- a/src/modules/bluetooth/module-bluez4-device.c
++++ b/src/modules/bluetooth/module-bluez4-device.c
+@@ -29,6 +29,9 @@
+ #include <math.h>
+ #include <linux/sockios.h>
+ #include <arpa/inet.h>
++#ifdef BLUETOOTH_APTX_SUPPORT
++#include <dlfcn.h>
++#endif
+ #include <pulse/rtclock.h>
+ #include <pulse/sample.h>
+@@ -107,6 +110,10 @@ struct a2dp_info {
+     bool sbc_initialized;                /* Keep track if the encoder is initialized */
+     size_t codesize, frame_length;       /* SBC Codesize, frame_length. We simply cache those values here */
++#ifdef BLUETOOTH_APTX_SUPPORT
++    pa_bool_t aptx_initialized;       /* Keep track if the encoder is initialized */
++    void *aptx;                       /* Codec data */
++#endif
+     void* buffer;                        /* Codec transfer buffer */
+     size_t buffer_size;                  /* Size of the buffer */
+@@ -207,6 +214,42 @@ enum {
+ static int init_profile(struct userdata *u);
++#ifdef BLUETOOTH_APTX_SUPPORT
++void* (*aptx_new)(short endian);
++int (*aptx_encode)(void* _state, void* _pcmL, void* _pcmR, void* _buffer);
++
++const char *aptx_new_name = "NewAptxEnc";
++const char *aptx_encode_name = "aptxbtenc_encodestereo";
++
++static pa_bool_t pa_load_aptx_sym(void *handle )
++{
++      if (!handle)
++              return FALSE;
++
++      aptx_new = (void* (*)(short endian))dlsym(handle, aptx_new_name);
++
++      if (aptx_new) {
++          pa_log_debug("Load Symbol(%s)", aptx_new_name);
++        } else {
++          pa_log_debug("Fail to Load Symbol(%s)", aptx_new_name);
++          return FALSE;
++      }
++
++      aptx_encode = (int (*)(void* _state, void* _pcmL, void* _pcmR,
++                                void* _buffer))
++                      dlsym(handle, "aptxbtenc_encodestereo");
++
++      if (aptx_encode) {
++          pa_log_debug("Load Symbol(%s)", aptx_encode_name);
++        } else {
++          pa_log_debug("Fail to Load Symbol(%s)", aptx_encode_name);
++          return FALSE;
++        }
++
++      return TRUE;
++}
++#endif
++
+ /* from IO thread */
+ static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
+     struct a2dp_info *a2dp;
+@@ -859,6 +902,123 @@ static int a2dp_process_render(struct userdata *u) {
+     return ret;
+ }
++#ifdef BLUETOOTH_APTX_SUPPORT
++/* Run from IO thread */
++static int a2dp_aptx_process_render(struct userdata *u) {
++    struct a2dp_info *a2dp;
++    size_t nbytes;
++    void *d;
++    const void *p;
++    size_t to_write, to_encode;
++    int ret = 0;
++
++    int pcmL[4],pcmR[4];
++    int i=0;
++    const short *mybuffer;
++
++    pa_assert(u);
++    pa_assert(u->profile == PROFILE_A2DP);
++    pa_assert(u->sink);
++
++    /* First, render some data */
++    if (!u->write_memchunk.memblock)
++        pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk);
++
++    pa_assert(u->write_memchunk.length == u->write_block_size);
++
++    a2dp_prepare_buffer(u);
++
++    a2dp = &u->a2dp;
++
++    /* Try to create a packet of the full MTU */
++    p = (const uint8_t*) pa_memblock_acquire(u->write_memchunk.memblock) + u->write_memchunk.index;
++    to_encode = u->write_memchunk.length;
++
++    d = (uint8_t*) a2dp->buffer ;
++    to_write = a2dp->buffer_size;
++
++    while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
++        size_t written;
++        ssize_t encoded;
++
++        mybuffer=(uint8_t *)p;
++
++        for (i = 0; i < 4; i += 1) {
++           pcmL[i] = mybuffer[2*i];
++           pcmR[i] = mybuffer[2*i+1];
++        }
++      /*(8 audio samples)16 bytes of audo data encoded to 4 bytes*/
++      aptx_encode(a2dp->aptx, pcmL, pcmR, (short*)d);
++
++        encoded=16;
++        written=4;
++
++        pa_assert_fp((size_t) encoded <= to_encode);
++        pa_assert_fp((size_t) written <= to_write);
++
++        p = (const uint8_t*) p + encoded;
++        to_encode -= encoded;
++
++        d = (uint8_t*) d + written;
++        to_write -= written;
++
++    }
++
++    pa_memblock_release(u->write_memchunk.memblock);
++
++    pa_assert(to_encode == 0);
++
++    PA_ONCE_BEGIN {
++        pa_log_debug("Using APTX encoder implementation");
++    } PA_ONCE_END;
++
++    nbytes = (uint8_t*) d - (uint8_t*) a2dp->buffer;
++
++    for (;;) {
++        ssize_t l;
++
++        l = pa_write(u->stream_fd, a2dp->buffer, nbytes, &u->stream_write_type);
++
++        pa_assert(l != 0);
++
++        if (l < 0) {
++
++            if (errno == EINTR)
++                /* Retry right away if we got interrupted */
++                continue;
++
++            else if (errno == EAGAIN)
++                /* Hmm, apparently the socket was not writable, give up for now */
++                break;
++
++            pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
++            ret  = -1;
++            break;
++        }
++
++        pa_assert((size_t) l <= nbytes);
++
++        if ((size_t) l != nbytes) {
++            pa_log_warn("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
++                        (unsigned long long) l,
++                        (unsigned long long) nbytes);
++            ret = -1;
++            break;
++        }
++
++        u->write_index += (uint64_t) u->write_memchunk.length;
++        pa_memblock_unref(u->write_memchunk.memblock);
++        pa_memchunk_reset(&u->write_memchunk);
++
++        ret = 1;
++
++        break;
++    }
++
++    return ret;
++}
++#endif
++
+ static int a2dp_process_push(struct userdata *u) {
+     int ret = 0;
+     pa_memchunk memchunk;
+@@ -1104,8 +1264,13 @@ static void thread_func(void *userdata) {
+                         u->started_at = pa_rtclock_now();
+                     if (u->profile == PA_BLUEZ4_PROFILE_A2DP) {
++#ifdef BLUETOOTH_APTX_SUPPORT
++                        if ((n_written = a2dp_aptx_process_render(u)) < 0)
++                                goto io_fail;
++#else
+                         if ((n_written = a2dp_process_render(u)) < 0)
+                             goto io_fail;
++#endif
+                     } else {
+                         if ((n_written = hsp_process_render(u)) < 0)
+                             goto io_fail;
+@@ -1689,14 +1854,73 @@ static int add_source(struct userdata *u) {
+     return 0;
+ }
++#ifdef BLUETOOTH_APTX_SUPPORT
++/* should be implemeted */
++static int bt_transport_config_a2dp_for_aptx(struct userdata *u) {
++    //const pa_bluetooth_transport *t;
++    struct a2dp_info *a2dp = &u->a2dp;
++    //a2dp_sbc_t *config;
++
++    //t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport);
++    //pa_assert(t);
++
++    //config = (a2dp_sbc_t *) t->config;
++
++    u->sample_spec.format = PA_SAMPLE_S16LE;
++
++    if (!a2dp->aptx_initialized){
++      #if __BYTE_ORDER==__LITTLE_ENDIAN
++              a2dp->aptx = aptx_new(1);
++      #elif __BYTE_ORDER==__BIG_ENDIAN
++              a2dp->aptx = aptx_new(0);
++      #else
++              #error "Unknown byte order"
++      #endif
++              a2dp->aptx_initialized = TRUE;
++    }
++
++    pa_log_debug("aptx Encoder is intialized !!");
++
++    u->write_block_size =(size_t)(u->write_link_mtu/(size_t)16) *16*4 ;
++    u->read_block_size =(size_t)(u->read_link_mtu/(size_t)16) *16*4 ;
++
++    pa_log_info("APTX parameters write_block_size(%d),write_link_mtu(%d)",u->write_block_size,u->write_link_mtu);
++    pa_log_info("APTX parameters read_block_size(%d),read_link_mtu(%d)",u->read_block_size,u->read_link_mtu);
++
++    return 0;
++}
++#endif
++
+ static void bt_transport_config_a2dp(struct userdata *u) {
+     const pa_bluez4_transport *t;
+     struct a2dp_info *a2dp = &u->a2dp;
+     a2dp_sbc_t *config;
++#ifdef BLUETOOTH_APTX_SUPPORT
++    a2dp_aptx_t *aptx_config;
++#endif
+     t = u->transport;
+     pa_assert(t);
++#ifdef BLUETOOTH_APTX_SUPPORT
++    if (t->codec == A2DP_CODEC_NON_A2DP) {
++        aptx_config = (a2dp_aptx_t *) t->config;
++        if (aptx_config->vendor_id[0] == APTX_VENDOR_ID0 &&
++            aptx_config->vendor_id[1] == APTX_VENDOR_ID1 &&
++            aptx_config->vendor_id[2] == APTX_VENDOR_ID2 &&
++            aptx_config->vendor_id[3] == APTX_VENDOR_ID3 &&
++            aptx_config->codec_id[0] == APTX_CODEC_ID0  &&
++            aptx_config->codec_id[1] == APTX_CODEC_ID1  ){
++            pa_log("A2DP_CODEC_NON_A2DP and this is APTX Codec");
++
++            return bt_transport_config_a2dp_for_aptx(u);
++        } else {
++            pa_log("A2DP_CODEC_NON_A2DP but this is not APTX Codec");
++            return -1;
++        }
++    }
++#endif
++
+     config = (a2dp_sbc_t *) t->config;
+     u->sample_spec.format = PA_SAMPLE_S16LE;
+@@ -2424,6 +2648,9 @@ int pa__init(pa_module *m) {
+     struct userdata *u;
+     const char *address, *path;
+     pa_bluez4_device *device;
++#ifdef BLUETOOTH_APTX_SUPPORT
++    void *handle;
++#endif
+     pa_assert(m);
+@@ -2524,6 +2751,15 @@ int pa__init(pa_module *m) {
+     u->msg->parent.process_msg = device_process_msg;
+     u->msg->card = u->card;
++#ifdef BLUETOOTH_APTX_SUPPORT
++    handle = pa_aptx_get_handle();
++
++    if (handle) {
++        pa_log_debug("Aptx Library loaded\n");
++        pa_load_aptx_sym(handle);
++    }
++#endif
++
+     if (u->profile != PA_BLUEZ4_PROFILE_OFF)
+         if (init_profile(u) < 0)
+             goto off;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0040-create-pa_ready-file-samsung.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0040-create-pa_ready-file-samsung.patch
new file mode 100644 (file)
index 0000000..191e70e
--- /dev/null
@@ -0,0 +1,40 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Thu, 8 Aug 2013 11:23:38 +0300
+Subject: create pa_ready file - samsung
+
+Change-Id: I2146599f2e814be064864f8ca76879b761642f11
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/daemon/main.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/src/daemon/main.c b/src/daemon/main.c
+index e01371e..74cb45f 100644
+--- a/src/daemon/main.c
++++ b/src/daemon/main.c
+@@ -38,6 +38,8 @@
+ #include <sys/types.h>
+ #include <sys/stat.h>
++#include <fcntl.h>
++
+ #ifdef HAVE_SYS_MMAN_H
+ #include <sys/mman.h>
+ #endif
+@@ -101,6 +103,7 @@
+ #include "ltdl-bind-now.h"
+ #include "server-lookup.h"
++#define PA_READY "/tmp/.pa_ready"
+ #ifdef HAVE_LIBWRAP
+ /* Only one instance of these variables */
+ int allow_severity = LOG_INFO;
+@@ -1145,6 +1148,8 @@ int main(int argc, char *argv[]) {
+ #endif
+     pa_log_info(_("Daemon startup complete."));
++    /* broadcast if we're ready */
++    creat(PA_READY, 0644);
+     retval = 0;
+     if (pa_mainloop_run(mainloop, &retval) < 0)
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0041-set-alsa-suspend-timeout-to-zero-samsung.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0041-set-alsa-suspend-timeout-to-zero-samsung.patch
new file mode 100644 (file)
index 0000000..9c6c9a4
--- /dev/null
@@ -0,0 +1,32 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Thu, 8 Aug 2013 11:24:25 +0300
+Subject: set alsa suspend timeout to zero - samsung
+
+Change-Id: Ie7c93c727d878226189f751efbd6e088ece7f36f
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/alsa/alsa-sink.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
+index 5a41cf6..03babb3 100644
+--- a/src/modules/alsa/alsa-sink.c
++++ b/src/modules/alsa/alsa-sink.c
+@@ -60,6 +60,7 @@
+ #include "alsa-util.h"
+ #include "alsa-sink.h"
++#define ALSA_SUSPEND_ON_IDLE_TIMEOUT  "0"
+ /* #define DEBUG_TIMING */
+ #define DEFAULT_DEVICE "default"
+@@ -2275,6 +2276,9 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
+     pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size));
+     pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial"));
++    /* Set Suspend timeout to ZERO to avoid noise */
++    pa_log_info("Set suspend-on-idle timeout to ZERO to avoid noise");
++    pa_proplist_sets(data.proplist, "module-suspend-on-idle.timeout", ALSA_SUSPEND_ON_IDLE_TIMEOUT);
+     if (mapping) {
+         pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, mapping->name);
+         pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, mapping->description);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0042-cope-with-possible-infinite-waiting-in-startup-samsu.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0042-cope-with-possible-infinite-waiting-in-startup-samsu.patch
new file mode 100644 (file)
index 0000000..4e91da4
--- /dev/null
@@ -0,0 +1,61 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Thu, 8 Aug 2013 11:27:44 +0300
+Subject: cope with possible infinite waiting in startup - samsung
+
+Change-Id: Ie7c74131e267f44324f031a953c15f81b0c31a07
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/core-util.c | 19 ++++++++++++++++++-
+ 1 file changed, 18 insertions(+), 1 deletion(-)
+
+diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c
+index 0d9e354..1256a1e 100644
+--- a/src/pulsecore/core-util.c
++++ b/src/pulsecore/core-util.c
+@@ -1698,6 +1698,7 @@ static char* make_random_dir(mode_t m) {
+     char *fn;
+     size_t pathlen;
++    srand (time(NULL));
+     fn = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse-XXXXXXXXXXXX", pa_get_temp_dir());
+     pathlen = strlen(fn);
+@@ -1763,6 +1764,7 @@ static int make_random_dir_and_link(mode_t m, const char *k) {
+ char *pa_get_runtime_dir(void) {
+     char *d, *k = NULL, *p = NULL, *t = NULL, *mid;
+     mode_t m;
++    int retry_count = 100;
+     /* The runtime directory shall contain dynamic data that needs NOT
+      * to be kept across reboots and is usually private to the user,
+@@ -1823,6 +1825,21 @@ char *pa_get_runtime_dir(void) {
+     for (;;) {
+         /* OK, first let's check if the "runtime" symlink already exists */
++        /* FIXME: This is recovery routine for infinite waiting issue such as below situation.
++         * eg. 50f64052a5dbbe087c11dfac4effb63c-runtime -> /tmp/pulse-LDK8gTL6Dh9N
++               50f64052a5dbbe087c11dfac4effb63c-runtime.tmp -> /tmp/pulse-cDM1bQhObZ7O */
++        if (retry_count-- == 0) {
++            pa_log_error ("retry is over....do cleanup");
++
++            /* Remove original file */
++            unlink (k);
++
++            /* Remove original.tmp file */
++            t = pa_sprintf_malloc("%s.tmp", k);
++            unlink (t);
++            pa_xfree(t);
++            t = NULL;
++        }
+         p = pa_readlink(k);
+         if (!p) {
+@@ -3297,7 +3314,7 @@ const char *pa_get_temp_dir(void) {
+         pa_is_path_absolute(t))
+         return t;
+-    return "/tmp";
++    return "/tmp/pulseaudio";
+ }
+ int pa_open_cloexec(const char *fn, int flags, mode_t mode) {
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0043-use-udev-only-for-usb-devices-samsung.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0043-use-udev-only-for-usb-devices-samsung.patch
new file mode 100644 (file)
index 0000000..d989b3f
--- /dev/null
@@ -0,0 +1,98 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Thu, 8 Aug 2013 11:28:39 +0300
+Subject: use udev only for usb devices - samsung
+
+Change-Id: Ia8cd2f5eb5ebe5248af11906c67d572ede133b33
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ configure.ac                        | 14 +++++++++++++-
+ src/modules/alsa/module-alsa-card.c |  7 +++++++
+ src/modules/module-udev-detect.c    | 17 +++++++++++++++++
+ 3 files changed, 37 insertions(+), 1 deletion(-)
+
+diff --git a/configure.ac b/configure.ac
+index ebff16c..13c8f6c 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -1115,8 +1115,18 @@ AC_SUBST(HAVE_UDEV)
+ AM_CONDITIONAL([HAVE_UDEV], [test "x$HAVE_UDEV" = x1])
+ AS_IF([test "x$HAVE_UDEV" = "x1"], AC_DEFINE([HAVE_UDEV], 1, [Have UDEV.]))
+-#### HAL compat support (optional, dependent on UDEV) ####
++#### udev for usb _only_ support (optional, dependant on UDEV) ####
++AC_ARG_ENABLE([udev_with_usb_only],
++    AS_HELP_STRING([--enable-udev-with-usb-only],[Enable UDEV with only USB support]))
++
++AS_IF([test "x$enable_udev_with_usb_only" != "xyes"],
++    [AS_IF([test "x$HAVE_UDEV" = "x1"], HAVE_UDEV_ONLY_USB=0, HAVE_UDEV_ONLY_USB=1)],
++    HAVE_UDEV_ONLY_USB=1)
++AM_CONDITIONAL([HAVE_UDEV_ONLY_USB], [test "x$HAVE_UDEV_ONLY_USB" = x1])
++AS_IF([test "x$HAVE_UDEV_ONLY_USB" = "x1"], AC_DEFINE([HAVE_UDEV_ONLY_USB], 1, [Have usb only with udev.]))
++
++#### HAL compat support (optional, dependent on UDEV) ####
+ AC_ARG_ENABLE([hal-compat],
+     AS_HELP_STRING([--disable-hal-compat],[Disable optional HAL->udev transition compatibility support]))
+@@ -1511,6 +1521,7 @@ AS_IF([test "x$HAVE_SYSTEMD_JOURNAL" = "x1"], ENABLE_SYSTEMD_JOURNAL=yes, ENABLE
+ AS_IF([test "x$HAVE_BLUEZ_4" = "x1"], ENABLE_BLUEZ_4=yes, ENABLE_BLUEZ_4=no)
+ AS_IF([test "x$HAVE_BLUEZ_5" = "x1"], ENABLE_BLUEZ_5=yes, ENABLE_BLUEZ_5=no)
+ AS_IF([test "x$HAVE_HAL_COMPAT" = "x1"], ENABLE_HAL_COMPAT=yes, ENABLE_HAL_COMPAT=no)
++AS_IF([test "x$HAVE_UDEV_ONLY_USB" = "x1"], ENABLE_UDEV_ONLY_USB=yes, ENABLE_UDEV_ONLY_USB=no)
+ AS_IF([test "x$HAVE_TCPWRAP" = "x1"], ENABLE_TCPWRAP=yes, ENABLE_TCPWRAP=no)
+ AS_IF([test "x$HAVE_LIBSAMPLERATE" = "x1"], ENABLE_LIBSAMPLERATE=yes, ENABLE_LIBSAMPLERATE=no)
+ AS_IF([test "x$HAVE_IPV6" = "x1"], ENABLE_IPV6=yes, ENABLE_IPV6=no)
+@@ -1566,6 +1577,7 @@ echo "
+         headset backed:            ${BLUETOOTH_HEADSET_BACKEND}
+     Enable udev:                   ${ENABLE_UDEV}
+       Enable HAL->udev compat:     ${ENABLE_HAL_COMPAT}
++      Enable udev usb only:        ${ENABLE_UDEV_ONLY_USB}
+     Enable systemd login:          ${ENABLE_SYSTEMD}
+     Enable systemd journal:        ${ENABLE_SYSTEMD_JOURNAL}
+     Enable TCP Wrappers:           ${ENABLE_TCPWRAP}
+diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
+index cf54c0f..1e63230 100644
+--- a/src/modules/alsa/module-alsa-card.c
++++ b/src/modules/alsa/module-alsa-card.c
+@@ -678,6 +678,13 @@ int pa__init(pa_module *m) {
+         u->use_ucm = false;
+ #ifdef HAVE_UDEV
+         fn = pa_udev_get_property(u->alsa_card_index, "PULSE_PROFILE_SET");
++#ifdef ENABLE_UDEV_ONLY_USB
++      pa_log("PULSE_PROFILE_SET = %s", fn);
++      if (fn == NULL) {
++            fn = strdup ("tizen_usb.conf");
++          pa_log("(new) PULSE_PROFILE_SET = %s", fn);
++    }
++#endif
+ #endif
+         if (pa_modargs_get_value(u->modargs, "profile_set", NULL)) {
+diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c
+index c28c867..1e8fb06 100644
+--- a/src/modules/module-udev-detect.c
++++ b/src/modules/module-udev-detect.c
+@@ -464,6 +464,23 @@ static void process_device(struct userdata *u, struct udev_device *dev) {
+         return;
+     }
++    pa_log_debug ("devpath = %s", udev_device_get_devpath(dev));
++    pa_log_debug ("subsystem = %s", udev_device_get_subsystem(dev));
++    pa_log_debug ("devtype = %s", udev_device_get_devtype(dev));
++    pa_log_debug ("syspath = %s", udev_device_get_syspath(dev));
++    pa_log_debug ("sysname = %s", udev_device_get_sysname(dev));
++    pa_log_debug ("sysnum = %s", udev_device_get_sysnum(dev));
++    pa_log_debug ("devnode = %s", udev_device_get_devnode(dev));
++    pa_log_debug ("parent subsystem = %s", udev_device_get_subsystem(udev_device_get_parent(dev)));
++
++#ifdef ENABLE_UDEV_ONLY_USB
++    /* If parent's subsystem is not USB, return */
++    if (!pa_streq(udev_device_get_subsystem(udev_device_get_parent(dev)), "usb")) {
++        pa_log_debug("Ignoring %s, because it's parent subsystem is not a USB.", udev_device_get_devpath(dev));
++        return;
++    }
++#endif
++
+     action = udev_device_get_action(dev);
+     if (action && pa_streq(action, "remove"))
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0044-fixes-and-improvements-to-makefile-and-configure-in-.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0044-fixes-and-improvements-to-makefile-and-configure-in-.patch
new file mode 100644 (file)
index 0000000..0cb735b
--- /dev/null
@@ -0,0 +1,120 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Fri, 7 Mar 2014 17:49:42 +0200
+Subject: fixes and improvements to makefile and configure in - samsung
+
+Change-Id: Ic2338a8382fe45f9d509537950592c9c4aa83606
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ configure.ac    | 14 +++++++++++++-
+ src/Makefile.am | 28 +++++++++-------------------
+ 2 files changed, 22 insertions(+), 20 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index 13c8f6c..870375f 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -1539,6 +1539,11 @@ AS_IF([test "x$HAVE_ESOUND" = "x1" -a "x$USE_PER_USER_ESOUND_SOCKET" = "x1"], EN
+ AS_IF([test "x$HAVE_GCOV" = "x1"], ENABLE_GCOV=yes, ENABLE_GCOV=no)
+ AS_IF([test "x$HAVE_LIBCHECK" = "x1"], ENABLE_TESTS=yes, ENABLE_TESTS=no)
+ AS_IF([test "x$enable_legacy_database_entry_format" != "xno"], ENABLE_LEGACY_DATABASE_ENTRY_FORMAT=yes, ENABLE_LEGACY_DATABASE_ENTRY_FORMAT=no)
++AS_IF([test "x$USE_SAMSUNG_POLICY" = "x1"], ENABLE_SAMSUNG_POLICY=yes, ENABLE_SAMSUNG_POLICY=no)
++AS_IF([test "x$USE_DLOG" = "x1"], ENABLE_DLOG=yes, ENABLE_DLOG=no)
++AS_IF([test "x$USE_PM_LOCK" = "x1"], ENABLE_PM_LOCK=yes, ENABLE_PM_LOCK=no)
++AS_IF([test "x$USE_BT_PROFILE_SET" = "x1"], ENABLE_BT_PROFILE_SET=yes, ENABLE_BT_PROFILE_SET=no)
++AS_IF([test "x$HAVE_BT_A2DP_APTX" = "x1"], HAVE_BT_A2DP_APTX=yes, HAVE_BT_A2DP_APTX=no)
+ echo "
+  ---{ $PACKAGE_NAME $VERSION }---
+@@ -1577,7 +1582,6 @@ echo "
+         headset backed:            ${BLUETOOTH_HEADSET_BACKEND}
+     Enable udev:                   ${ENABLE_UDEV}
+       Enable HAL->udev compat:     ${ENABLE_HAL_COMPAT}
+-      Enable udev usb only:        ${ENABLE_UDEV_ONLY_USB}
+     Enable systemd login:          ${ENABLE_SYSTEMD}
+     Enable systemd journal:        ${ENABLE_SYSTEMD_JOURNAL}
+     Enable TCP Wrappers:           ${ENABLE_TCPWRAP}
+@@ -1604,6 +1608,14 @@ echo "
+     Preopened modules:             ${PREOPEN_MODS}
+     Legacy Database Entry Support: ${ENABLE_LEGACY_DATABASE_ENTRY_FORMAT}
++
++    Tizen
++      samsung policy:              ${ENABLE_SAMSUNG_POLICY}
++      dlog:                        ${ENABLE_DLOG}
++      pmapi:                       ${ENABLE_PM_LOCK}
++      bluetooth profile set:       ${ENABLE_BT_PROFILE_SET}
++      bluetooth aptx codec:        ${HAVE_BT_A2DP_APTX}
++      udev with usb only:          ${ENABLE_UDEV_ONLY_USB}
+ "
+ if test "${ENABLE_SPEEX}" = "no" && test "${ENABLE_WEBRTC}" = "no" && test "${ENABLE_ADRIAN_EC}" = "no" ; then
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 3c062eb..a60b5bf 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -2078,10 +2078,9 @@ module_bluetooth_policy_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+ module_bluetooth_discover_la_SOURCES = modules/bluetooth/module-bluetooth-discover.c
+ module_bluetooth_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
+ module_bluetooth_discover_la_LIBADD = $(MODULE_LIBADD)
+-if HAVE_BT_A2DP_APTX
+-module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS) -DBLUETOOTH_APTX_SUPPORT
+-else
+ module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS)
++if HAVE_BT_A2DP_APTX
++module_bluetooth_discover_la_CFLAGS += -DBLUETOOTH_APTX_SUPPORT
+ endif
+ # Bluetooth BlueZ 4 sink / source
+@@ -2096,22 +2095,17 @@ libbluez4_util_la_SOURCES = \
+               modules/bluetooth/bluez4-util.h
+ libbluez4_util_la_LDFLAGS = -avoid-version
+ libbluez4_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
+-
+-if HAVE_BT_A2DP_APTX
+-libbluez4_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -DBLUETOOTH_APTX_SUPPORT
+-else
+ libbluez4_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
++if HAVE_BT_A2DP_APTX
++libbluez4_util_la_CFLAGS += -DBLUETOOTH_APTX_SUPPORT
+ endif
+ module_bluez4_device_la_SOURCES = modules/bluetooth/module-bluez4-device.c modules/bluetooth/rtp.h
+ module_bluez4_device_la_LDFLAGS = $(MODULE_LDFLAGS)
+ module_bluez4_device_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS) libbluez4-util.la
+-
+-if HAVE_BT_A2DP_APTX
+-module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS) \
+-                                    -DBLUETOOTH_APTX_SUPPORT
+-else
+ module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
++if HAVE_BT_A2DP_APTX
++module_bluez4_device_la_CFLAGS += -DBLUETOOTH_APTX_SUPPORT
+ endif
+ # Bluetooth BlueZ 5 sink / source
+@@ -2123,11 +2117,10 @@ libbluez5_util_la_SOURCES = \
+               modules/bluetooth/hfaudioagent-@BLUETOOTH_HEADSET_BACKEND@.c
+ libbluez5_util_la_LDFLAGS = -avoid-version
+ libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
++libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+ if HAVE_BT_A2DP_APTX
+-libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -DBLUETOOTH_APTX_SUPPORT
+-else
+-libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
++libbluez5_util_la_CFLAGS += -DBLUETOOTH_APTX_SUPPORT
+ endif
+ module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c
+@@ -2141,10 +2134,7 @@ module_bluez5_device_la_LIBADD = $(MODULE_LIBADD) $(SBC_LIBS) libbluez5-util.la
+ module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) $(SBC_CFLAGS)
+ if HAVE_BT_A2DP_APTX
+-module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) $(SBC_CFLAGS) \
+-                                    -DBLUETOOTH_APTX_SUPPORT
+-else
+-module_bluez5_device_la_CFLAGS = $(AM_CFLAGS) $(SBC_CFLAGS)
++module_bluez5_device_la_CFLAGS += -DBLUETOOTH_APTX_SUPPORT
+ endif
+ # Apple Airtunes/RAOP
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0045-fix-warning-in-gconf-helper.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0045-fix-warning-in-gconf-helper.patch
new file mode 100644 (file)
index 0000000..b89ec5a
--- /dev/null
@@ -0,0 +1,24 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Tue, 11 Mar 2014 12:47:52 +0200
+Subject: fix warning in gconf helper
+
+Change-Id: Id75cd24cfd1c0d62d4c227b6715dc0d9d5ea6b1f
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/gconf/gconf-helper.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/modules/gconf/gconf-helper.c b/src/modules/gconf/gconf-helper.c
+index fbd8cfd..4681748 100644
+--- a/src/modules/gconf/gconf-helper.c
++++ b/src/modules/gconf/gconf-helper.c
+@@ -99,7 +99,9 @@ int main(int argc, char *argv[]) {
+     GConfClient *client;
+     GSList *modules, *m;
++#if !defined(GLIB_VERSION_2_36)
+     g_type_init();
++#endif
+     if (!(client = gconf_client_get_default()))
+         goto fail;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0046-volume-ramp-add-client-api-support-for-volume-rampin.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0046-volume-ramp-add-client-api-support-for-volume-rampin.patch
new file mode 100644 (file)
index 0000000..9cc07b6
--- /dev/null
@@ -0,0 +1,461 @@
+From: Jaska Uimonen <jaska.uimonen@helsinki.fi>
+Date: Wed, 8 Aug 2012 11:14:40 +0300
+Subject: volume ramp: add client api support for volume ramping
+
+Change-Id: I6afc7540af68400db54eec258bdf4a80c311bb69
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/map-file                    |  3 ++
+ src/pulse/introspect.c          | 74 +++++++++++++++++++++++++++++++++++++
+ src/pulse/introspect.h          |  9 +++++
+ src/pulse/stream.c              |  6 ++-
+ src/pulsecore/native-common.h   |  3 ++
+ src/pulsecore/pdispatch.c       |  2 +
+ src/pulsecore/protocol-native.c | 81 ++++++++++++++++++++++++++++++++++++++++-
+ src/pulsecore/tagstruct.c       | 74 +++++++++++++++++++++++++++++++++++++
+ src/pulsecore/tagstruct.h       |  3 ++
+ 9 files changed, 252 insertions(+), 3 deletions(-)
+
+diff --git a/src/map-file b/src/map-file
+index d51596c..20e577a 100644
+--- a/src/map-file
++++ b/src/map-file
+@@ -94,12 +94,15 @@ pa_context_set_event_callback;
+ pa_context_set_name;
+ pa_context_set_sink_input_mute;
+ pa_context_set_sink_input_volume;
++pa_context_set_sink_input_volume_ramp;
+ pa_context_set_sink_mute_by_index;
+ pa_context_set_sink_mute_by_name;
+ pa_context_set_sink_port_by_index;
+ pa_context_set_sink_port_by_name;
+ pa_context_set_sink_volume_by_index;
+ pa_context_set_sink_volume_by_name;
++pa_context_set_sink_volume_ramp_by_index;
++pa_context_set_sink_volume_ramp_by_name;
+ pa_context_set_source_output_mute;
+ pa_context_set_source_output_volume;
+ pa_context_set_source_mute_by_index;
+diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c
+index 2d54fdb..a72020a 100644
+--- a/src/pulse/introspect.c
++++ b/src/pulse/introspect.c
+@@ -1446,6 +1446,56 @@ pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name
+     return o;
+ }
++pa_operation* pa_context_set_sink_volume_ramp_by_index(pa_context *c, uint32_t idx, const pa_cvolume_ramp *ramp, pa_context_success_cb_t cb, void *userdata) {
++    pa_operation *o;
++    pa_tagstruct *t;
++    uint32_t tag;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++    pa_assert(ramp);
++
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++
++    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++    t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_VOLUME_RAMP, &tag);
++    pa_tagstruct_putu32(t, idx);
++    pa_tagstruct_puts(t, NULL);
++    pa_tagstruct_put_cvolume_ramp(t, ramp);
++    pa_pstream_send_tagstruct(c->pstream, t);
++    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++    return o;
++}
++
++pa_operation* pa_context_set_sink_volume_ramp_by_name(pa_context *c, const char *name, const pa_cvolume_ramp *ramp, pa_context_success_cb_t cb, void *userdata) {
++    pa_operation *o;
++    pa_tagstruct *t;
++    uint32_t tag;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++    pa_assert(name);
++    pa_assert(ramp);
++
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID);
++
++    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++    t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_VOLUME_RAMP, &tag);
++    pa_tagstruct_putu32(t, PA_INVALID_INDEX);
++    pa_tagstruct_puts(t, name);
++    pa_tagstruct_put_cvolume_ramp(t, ramp);
++    pa_pstream_send_tagstruct(c->pstream, t);
++    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++    return o;
++}
++
+ pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) {
+     pa_operation *o;
+     pa_tagstruct *t;
+@@ -1543,6 +1593,30 @@ pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mu
+     return o;
+ }
++pa_operation* pa_context_set_sink_input_volume_ramp(pa_context *c, uint32_t idx, const pa_cvolume_ramp *ramp, pa_context_success_cb_t cb, void *userdata) {
++    pa_operation *o;
++    pa_tagstruct *t;
++    uint32_t tag;
++
++    pa_assert(c);
++    pa_assert(PA_REFCNT_VALUE(c) >= 1);
++    pa_assert(ramp);
++
++    PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
++    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
++
++    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
++
++    t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_INPUT_VOLUME_RAMP, &tag);
++    pa_tagstruct_putu32(t, idx);
++    pa_tagstruct_puts(t, NULL);
++    pa_tagstruct_put_cvolume_ramp(t, ramp);
++    pa_pstream_send_tagstruct(c->pstream, t);
++    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
++
++    return o;
++}
++
+ pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) {
+     pa_operation *o;
+     pa_tagstruct *t;
+diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h
+index 22fefed..127cade 100644
+--- a/src/pulse/introspect.h
++++ b/src/pulse/introspect.h
+@@ -287,6 +287,12 @@ pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int
+ /** Set the mute switch of a sink device specified by its name */
+ pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata);
++/** Set the volume ramp of a sink device specified by its index */
++pa_operation* pa_context_set_sink_volume_ramp_by_index(pa_context *c, uint32_t idx, const pa_cvolume_ramp *ramp, pa_context_success_cb_t cb, void *userdata);
++
++/** Set the volume ramp of a sink device specified by its name */
++pa_operation* pa_context_set_sink_volume_ramp_by_name(pa_context *c, const char *name, const pa_cvolume_ramp *ramp, pa_context_success_cb_t cb, void *userdata);
++
+ /** Suspend/Resume a sink. \since 0.9.7 */
+ pa_operation* pa_context_suspend_sink_by_name(pa_context *c, const char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata);
+@@ -604,6 +610,9 @@ pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mu
+ /** Kill a sink input. */
+ pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
++/** Set the volume ramp of a sink input specified by its index */
++pa_operation* pa_context_set_sink_input_volume_ramp(pa_context *c, uint32_t idx, const pa_cvolume_ramp *ramp, pa_context_success_cb_t cb, void *userdata);
++
+ /** @} */
+ /** @{ \name Source Outputs */
+diff --git a/src/pulse/stream.c b/src/pulse/stream.c
+index 8e35c29..501b5b6 100644
+--- a/src/pulse/stream.c
++++ b/src/pulse/stream.c
+@@ -1213,7 +1213,8 @@ static int create_stream(
+                                               PA_STREAM_START_UNMUTED|
+                                               PA_STREAM_FAIL_ON_SUSPEND|
+                                               PA_STREAM_RELATIVE_VOLUME|
+-                                              PA_STREAM_PASSTHROUGH)), PA_ERR_INVALID);
++                                              PA_STREAM_PASSTHROUGH|
++                                              PA_STREAM_START_RAMP_MUTED)), PA_ERR_INVALID);
+     PA_CHECK_VALIDITY(s->context, s->context->version >= 12 || !(flags & PA_STREAM_VARIABLE_RATE), PA_ERR_NOTSUPPORTED);
+     PA_CHECK_VALIDITY(s->context, s->context->version >= 13 || !(flags & PA_STREAM_PEAK_DETECT), PA_ERR_NOTSUPPORTED);
+@@ -1372,6 +1373,9 @@ static int create_stream(
+         pa_tagstruct_put_boolean(t, flags & (PA_STREAM_PASSTHROUGH));
+     }
++    if (s->context->version >= 22 && s->direction == PA_STREAM_PLAYBACK)
++        pa_tagstruct_put_boolean(t, flags & (PA_STREAM_START_RAMP_MUTED));
++
+     pa_pstream_send_tagstruct(s->context->pstream, t);
+     pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL);
+diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h
+index dad82e0..301e2e7 100644
+--- a/src/pulsecore/native-common.h
++++ b/src/pulsecore/native-common.h
+@@ -176,6 +176,9 @@ enum {
+     /* Supported since protocol v27 (3.0) */
+     PA_COMMAND_SET_PORT_LATENCY_OFFSET,
++    PA_COMMAND_SET_SINK_VOLUME_RAMP,
++    PA_COMMAND_SET_SINK_INPUT_VOLUME_RAMP,
++
+     PA_COMMAND_MAX
+ };
+diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c
+index 4033240..8643378 100644
+--- a/src/pulsecore/pdispatch.c
++++ b/src/pulsecore/pdispatch.c
+@@ -190,6 +190,8 @@ static const char *command_names[PA_COMMAND_MAX] = {
+     [PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME] = "SET_SOURCE_OUTPUT_VOLUME",
+     [PA_COMMAND_SET_SOURCE_OUTPUT_MUTE] = "SET_SOURCE_OUTPUT_MUTE",
++    [PA_COMMAND_SET_SINK_VOLUME_RAMP] = "SET_SINK_VOLUME_RAMP",
++    [PA_COMMAND_SET_SINK_INPUT_VOLUME_RAMP] = "SET_SINK_INPUT_VOLUME_RAMP",
+ };
+ #endif
+diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
+index 41b4b50..4304cd4 100644
+--- a/src/pulsecore/protocol-native.c
++++ b/src/pulsecore/protocol-native.c
+@@ -294,6 +294,7 @@ static void command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag,
+ static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+ static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+ static void command_set_port_latency_offset(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
++static void command_set_volume_ramp(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
+     [PA_COMMAND_ERROR] = NULL,
+@@ -397,6 +398,9 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
+     [PA_COMMAND_SET_PORT_LATENCY_OFFSET] = command_set_port_latency_offset,
++    [PA_COMMAND_SET_SINK_VOLUME_RAMP] = command_set_volume_ramp,
++    [PA_COMMAND_SET_SINK_INPUT_VOLUME_RAMP] = command_set_volume_ramp,
++
+     [PA_COMMAND_EXTENSION] = command_extension
+ };
+@@ -1994,7 +1998,8 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
+         muted_set = false,
+         fail_on_suspend = false,
+         relative_volume = false,
+-        passthrough = false;
++        passthrough = false,
++        ramp_muted = false;
+     pa_sink_input_flags_t flags = 0;
+     pa_proplist *p = NULL;
+@@ -2122,6 +2127,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
+             }
+             pa_idxset_put(formats, format, NULL);
+         }
++
+     }
+     if (n_formats == 0) {
+@@ -2134,6 +2140,11 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
+         }
+     }
++    if (pa_tagstruct_get_boolean(t, &ramp_muted) < 0 ) {
++        protocol_error(c);
++        goto finish;
++    }
++
+     if (!pa_tagstruct_eof(t)) {
+         protocol_error(c);
+         goto finish;
+@@ -2165,7 +2176,8 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
+         (variable_rate ? PA_SINK_INPUT_VARIABLE_RATE : 0) |
+         (dont_inhibit_auto_suspend ? PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND : 0) |
+         (fail_on_suspend ? PA_SINK_INPUT_NO_CREATE_ON_SUSPEND|PA_SINK_INPUT_KILL_ON_SUSPEND : 0) |
+-        (passthrough ? PA_SINK_INPUT_PASSTHROUGH : 0);
++        (passthrough ? PA_SINK_INPUT_PASSTHROUGH : 0) |
++        (ramp_muted ? PA_SINK_INPUT_START_RAMP_MUTED : 0);
+     /* Only since protocol version 15 there's a separate muted_set
+      * flag. For older versions we synthesize it here */
+@@ -3797,6 +3809,71 @@ static void command_set_volume(
+     pa_pstream_send_simple_ack(c->pstream, tag);
+ }
++static void command_set_volume_ramp(
++        pa_pdispatch *pd,
++        uint32_t command,
++        uint32_t tag,
++        pa_tagstruct *t,
++        void *userdata) {
++
++    pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
++    uint32_t idx;
++    pa_cvolume_ramp ramp;
++    pa_sink *sink = NULL;
++    pa_sink_input *si = NULL;
++    const char *name = NULL;
++    const char *client_name;
++
++    pa_native_connection_assert_ref(c);
++    pa_assert(t);
++
++    if (pa_tagstruct_getu32(t, &idx) < 0 ||
++        (command == PA_COMMAND_SET_SINK_VOLUME_RAMP && pa_tagstruct_gets(t, &name) < 0) ||
++        (command == PA_COMMAND_SET_SINK_INPUT_VOLUME_RAMP && pa_tagstruct_gets(t, &name) < 0) ||
++        pa_tagstruct_get_cvolume_ramp(t, &ramp) ||
++        !pa_tagstruct_eof(t)) {
++        protocol_error(c);
++        return;
++    }
++
++    CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
++    CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SET_SINK_VOLUME ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID);
++    CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX || name, tag, PA_ERR_INVALID);
++    CHECK_VALIDITY(c->pstream, idx == PA_INVALID_INDEX || !name, tag, PA_ERR_INVALID);
++    CHECK_VALIDITY(c->pstream, !name || idx == PA_INVALID_INDEX, tag, PA_ERR_INVALID);
++
++    switch (command) {
++
++        case PA_COMMAND_SET_SINK_VOLUME_RAMP:
++            if (idx != PA_INVALID_INDEX)
++                sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx);
++            else
++                sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK);
++            break;
++
++        case PA_COMMAND_SET_SINK_INPUT_VOLUME_RAMP:
++            si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);
++            break;
++
++        default:
++            pa_assert_not_reached();
++    }
++
++    CHECK_VALIDITY(c->pstream, sink || si, tag, PA_ERR_NOENTITY);
++
++    client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY));
++
++    if (sink) {
++        pa_log_debug("Client %s changes volume ramp of sink %s.", client_name, sink->name);
++        pa_sink_set_volume_ramp(sink, &ramp, TRUE, TRUE);
++    } else if (si) {
++        pa_log_debug("Client %s changes volume ramp of sink input %s.", client_name, pa_strnull(pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME)));
++        pa_sink_input_set_volume_ramp(si, &ramp, TRUE, TRUE);
++    }
++
++    pa_pstream_send_simple_ack(c->pstream, tag);
++}
++
+ static void command_set_mute(
+         pa_pdispatch *pd,
+         uint32_t command,
+diff --git a/src/pulsecore/tagstruct.c b/src/pulsecore/tagstruct.c
+index e51fcf2..aba0178 100644
+--- a/src/pulsecore/tagstruct.c
++++ b/src/pulsecore/tagstruct.c
+@@ -256,6 +256,35 @@ void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume) {
+     }
+ }
++void pa_tagstruct_put_cvolume_ramp(pa_tagstruct *t, const pa_cvolume_ramp *ramp) {
++    unsigned i;
++    pa_volume_ramp_type_t type;
++    long length;
++    pa_volume_t target;
++
++    pa_assert(t);
++    pa_assert(ramp);
++    extend(t, 2 + ramp->channels * (sizeof(pa_volume_ramp_type_t) + sizeof(long) + sizeof(pa_volume_t)));
++
++    t->data[t->length++] = PA_TAG_CVOLUME_RAMP;
++    t->data[t->length++] = ramp->channels;
++
++    for (i = 0; i < ramp->channels; i++) {
++        type = htonl(ramp->ramps[i].type);
++        target = htonl(ramp->ramps[i].target);
++        length = htonl(ramp->ramps[i].length);
++
++        memcpy(t->data + t->length, &type, sizeof(pa_volume_ramp_type_t));
++        t->length += sizeof(pa_volume_ramp_type_t);
++
++        memcpy(t->data + t->length, &length, sizeof(long));
++        t->length += sizeof(long);
++
++        memcpy(t->data + t->length, &target, sizeof(pa_volume_t));
++        t->length += sizeof(pa_volume_t);
++    }
++}
++
+ void pa_tagstruct_put_volume(pa_tagstruct *t, pa_volume_t vol) {
+     uint32_t u;
+     pa_assert(t);
+@@ -579,6 +608,51 @@ int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *cvolume) {
+     return 0;
+ }
++int pa_tagstruct_get_cvolume_ramp(pa_tagstruct *t, pa_cvolume_ramp *ramp) {
++    unsigned i;
++    pa_volume_ramp_type_t type;
++    long length;
++    pa_volume_t target;
++    uint8_t *read_ptr;
++
++    pa_assert(t);
++    pa_assert(ramp);
++
++    if (t->rindex+2 > t->length)
++        return -1;
++
++    if (t->data[t->rindex] != PA_TAG_CVOLUME_RAMP)
++        return -1;
++
++    if ((ramp->channels = t->data[t->rindex+1]) > PA_CHANNELS_MAX)
++        return -1;
++
++    if (t->rindex+2+ramp->channels*(sizeof(pa_volume_ramp_type_t)+sizeof(long)+sizeof(pa_volume_t)) > t->length)
++        return -1;
++
++    read_ptr = t->data + t->rindex + 2;
++
++    for (i = 0; i < ramp->channels; i++) {
++        memcpy(&type, read_ptr, sizeof(pa_volume_ramp_type_t));
++        ramp->ramps[i].type = (pa_volume_ramp_type_t) ntohl(type);
++        read_ptr += sizeof(pa_volume_ramp_type_t);
++
++
++        memcpy(&length, read_ptr, sizeof(long));
++        ramp->ramps[i].length = (long) ntohl(length);
++        read_ptr += sizeof(long);
++
++
++        memcpy(&target, read_ptr, sizeof(pa_volume_t));
++        ramp->ramps[i].target = (pa_volume_t) ntohl(target);
++        read_ptr += sizeof(pa_volume_t);
++    }
++
++    t->rindex = read_ptr - t->data;
++
++    return 0;
++}
++
+ int pa_tagstruct_get_volume(pa_tagstruct*t, pa_volume_t *vol) {
+     uint32_t u;
+diff --git a/src/pulsecore/tagstruct.h b/src/pulsecore/tagstruct.h
+index 9fef255..4634bd6 100644
+--- a/src/pulsecore/tagstruct.h
++++ b/src/pulsecore/tagstruct.h
+@@ -60,6 +60,7 @@ enum {
+     PA_TAG_PROPLIST = 'P',
+     PA_TAG_VOLUME = 'V',
+     PA_TAG_FORMAT_INFO = 'f',
++    PA_TAG_CVOLUME_RAMP = 'J'
+ };
+ pa_tagstruct *pa_tagstruct_new(const uint8_t* data, size_t length);
+@@ -86,6 +87,7 @@ void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume);
+ void pa_tagstruct_put_proplist(pa_tagstruct *t, pa_proplist *p);
+ void pa_tagstruct_put_volume(pa_tagstruct *t, pa_volume_t volume);
+ void pa_tagstruct_put_format_info(pa_tagstruct *t, pa_format_info *f);
++void pa_tagstruct_put_cvolume_ramp(pa_tagstruct *t, const pa_cvolume_ramp *ramp);
+ int pa_tagstruct_get(pa_tagstruct *t, ...);
+@@ -104,5 +106,6 @@ int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *v);
+ int pa_tagstruct_get_proplist(pa_tagstruct *t, pa_proplist *p);
+ int pa_tagstruct_get_volume(pa_tagstruct *t, pa_volume_t *v);
+ int pa_tagstruct_get_format_info(pa_tagstruct *t, pa_format_info *f);
++int pa_tagstruct_get_cvolume_ramp(pa_tagstruct *t, pa_cvolume_ramp *ramp);
+ #endif
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0047-adjust-default-bluetooth-profile-to-off.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0047-adjust-default-bluetooth-profile-to-off.patch
new file mode 100644 (file)
index 0000000..4f20de2
--- /dev/null
@@ -0,0 +1,23 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Wed, 14 Aug 2013 15:00:52 +0300
+Subject: adjust default bluetooth profile to off
+
+Change-Id: I95ca525b0d20c1a864a0c66060767bb4c2160400
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/bluetooth/module-bluez4-device.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/modules/bluetooth/module-bluez4-device.c b/src/modules/bluetooth/module-bluez4-device.c
+index eba92c6..6b775b7 100644
+--- a/src/modules/bluetooth/module-bluez4-device.c
++++ b/src/modules/bluetooth/module-bluez4-device.c
+@@ -2527,6 +2527,8 @@ static int add_card(struct userdata *u) {
+         else
+             pa_log_warn("Profile '%s' not valid or not supported by device.", default_profile);
+     }
++    else
++      pa_card_new_data_set_profile(&data, p->name);
+     u->card = pa_card_new(u->core, &data);
+     pa_card_new_data_done(&data);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0048-Add-bt_profile_set-patch-which-fixed-bt-a2dp-hsp-pro.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0048-Add-bt_profile_set-patch-which-fixed-bt-a2dp-hsp-pro.patch
new file mode 100644 (file)
index 0000000..83227f7
--- /dev/null
@@ -0,0 +1,83 @@
+From: "vivian,zhang" <vivian.zhang@intel.com>
+Date: Wed, 17 Jul 2013 11:17:40 +0800
+Subject: Add bt_profile_set patch which fixed bt a2dp&hsp profile setting
+ issues in mobile
+
+Change-Id: I9bc3649b02ab7ac56584211789a3ea18ff17fbb7
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ configure.ac                                   | 13 +++++++++++++
+ src/Makefile.am                                |  3 +++
+ src/modules/bluetooth/module-bluez4-discover.c | 18 ++++++++++++++++++
+ 3 files changed, 34 insertions(+)
+
+diff --git a/configure.ac b/configure.ac
+index 870375f..6a6bc83 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -679,6 +679,19 @@ fi
+ AM_CONDITIONAL(USE_DLOG, test "x$USE_DLOG" = "xyes")
+ dnl end --------------------------------------------------------------------
++dnl use bt-profile-set --------------------------------------------------------------------------
++AC_ARG_ENABLE(bt-profile-set, AC_HELP_STRING([--enable-bt-profile-set], [enable bt profile param]),
++[
++ case "${enableval}" in
++        yes) USE_BT_PROFILE_SET=yes ;;
++        no)  USE_BT_PROFILE_SET=no ;;
++        *)   AC_MSG_ERROR(bad value ${enableval} for --enable-bt-profile-set) ;;
++ esac
++ ],[USE_BT_PROFILE_SET=no])
++
++AM_CONDITIONAL(USE_BT_PROFILE_SET, test "x$USE_BT_PROFILE_SET" = "xyes")
++dnl end --------------------------------------------------------------------
++
+ #### atomic-ops ####
+ AC_MSG_CHECKING([whether we need libatomic_ops])
+diff --git a/src/Makefile.am b/src/Makefile.am
+index a60b5bf..b09903f 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -2082,6 +2082,9 @@ module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS)
+ if HAVE_BT_A2DP_APTX
+ module_bluetooth_discover_la_CFLAGS += -DBLUETOOTH_APTX_SUPPORT
+ endif
++if USE_BT_PROFILE_SET
++module_bluetooth_discover_la_CFLAGS += -DBLUETOOTH_PROFILE_SET
++endif
+ # Bluetooth BlueZ 4 sink / source
+ module_bluez4_discover_la_SOURCES = modules/bluetooth/module-bluez4-discover.c
+diff --git a/src/modules/bluetooth/module-bluez4-discover.c b/src/modules/bluetooth/module-bluez4-discover.c
+index 7673ba7..aaaa095 100644
+--- a/src/modules/bluetooth/module-bluez4-discover.c
++++ b/src/modules/bluetooth/module-bluez4-discover.c
+@@ -79,9 +79,27 @@ static pa_hook_result_t load_module_for_device(pa_bluez4_discovery *y, const pa_
+             pa_module *m = NULL;
+             char *args;
++#ifdef BLUETOOTH_PROFILE_SET
++            const char *profile = NULL;
++
++            if ((d->transports[PROFILE_A2DP] && d->transports[PROFILE_A2DP]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)) {
++                profile = "a2dp";
++            } if ((d->transports[PROFILE_A2DP_SOURCE] && d->transports[PROFILE_A2DP_SOURCE]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)) {
++                profile = "a2dp_source";
++            } else if ((d->transports[PROFILE_HFGW] && d->transports[PROFILE_HFGW]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)) {
++                profile = "hfgw";
++            }
++            if (!profile)
++                return PA_HOOK_OK;
++#endif
++
+             /* Oh, awesome, a new device has shown up and been connected! */
++#ifdef BLUETOOTH_PROFILE_SET
++            args = pa_sprintf_malloc("address=\"%s\" path=\"%s\" profile=\"%s\"", d->address, d->path, profile);
++#else
+             args = pa_sprintf_malloc("address=\"%s\" path=\"%s\"", d->address, d->path);
++#endif
+             if (pa_modargs_get_value(u->modargs, "sco_sink", NULL) &&
+                 pa_modargs_get_value(u->modargs, "sco_source", NULL)) {
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0049-added-pulseaudio.service-file.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0049-added-pulseaudio.service-file.patch
new file mode 100644 (file)
index 0000000..c4c125c
--- /dev/null
@@ -0,0 +1,27 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Tue, 11 Jun 2013 17:03:27 +0300
+Subject: added pulseaudio.service file
+
+Change-Id: I8efef41060189f116be49b3455c588d67f045f82
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ pulseaudio.service | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+ create mode 100644 pulseaudio.service
+
+diff --git a/pulseaudio.service b/pulseaudio.service
+new file mode 100644
+index 0000000..f9d8592
+--- /dev/null
++++ b/pulseaudio.service
+@@ -0,0 +1,10 @@
++[Unit]
++Description=pulseaudio service
++After=syslog.target dbus.service
++
++[Service]
++Type=simple
++ExecStart=/usr/bin/pulseaudio --system
++
++[Install]
++WantedBy=multi-user.target
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0050-.gitignore-Add-pulsecore-config.h.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0050-.gitignore-Add-pulsecore-config.h.patch
new file mode 100644 (file)
index 0000000..c5c1d3b
--- /dev/null
@@ -0,0 +1,19 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 24 Feb 2014 12:38:31 +0200
+Subject: .gitignore: Add pulsecore-config.h
+
+Change-Id: I8409f1964e65e79669eaeb4930c6d536417a0b05
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ .gitignore | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/.gitignore b/.gitignore
+index f41ee72..b4a465e 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -34,3 +34,4 @@ missing
+ mkinstalldirs
+ stamp-*
+ .dirstamp
++pulsecore-config.h
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0051-pactl-Fix-crash-with-older-servers.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0051-pactl-Fix-crash-with-older-servers.patch
new file mode 100644 (file)
index 0000000..cd4736c
--- /dev/null
@@ -0,0 +1,405 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Sun, 16 Feb 2014 14:30:38 +0200
+Subject: pactl: Fix crash with older servers
+
+Servers older than 0.9.15 don't know anything about cards, and card
+operations will return a NULL pa_operation object when connected to
+that old server. We must check the pa_operation pointer before passing
+it to pa_operation_unref(), otherwise a NULL operation will result in
+a crash.
+
+Change-Id: Idc258479c077aafaff6254b76acb18034a158107
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/utils/pactl.c | 203 +++++++++++++++++++++++++++++++++++-------------------
+ 1 file changed, 133 insertions(+), 70 deletions(-)
+
+diff --git a/src/utils/pactl.c b/src/utils/pactl.c
+index 1d8faa4..958d700 100644
+--- a/src/utils/pactl.c
++++ b/src/utils/pactl.c
+@@ -94,7 +94,7 @@ static pa_stream *sample_stream = NULL;
+ static pa_sample_spec sample_spec;
+ static pa_channel_map channel_map;
+ static size_t sample_length = 0;
+-static int actions = 1;
++static int actions = 0;
+ static bool nl = false;
+@@ -1046,6 +1046,7 @@ static void set_sink_formats(pa_context *c, uint32_t sink, const char *str) {
+     char *format = NULL;
+     const char *state = NULL;
+     int i = 0;
++    pa_operation *o = NULL;
+     while ((format = pa_split(str, ";", &state))) {
+         pa_format_info *f = pa_format_info_from_string(pa_strip(format));
+@@ -1059,7 +1060,11 @@ static void set_sink_formats(pa_context *c, uint32_t sink, const char *str) {
+         pa_xfree(format);
+     }
+-    pa_operation_unref(pa_ext_device_restore_save_formats(c, PA_DEVICE_TYPE_SINK, sink, i, f_arr, simple_callback, NULL));
++    o = pa_ext_device_restore_save_formats(c, PA_DEVICE_TYPE_SINK, sink, i, f_arr, simple_callback, NULL);
++    if (o) {
++        pa_operation_unref(o);
++        actions++;
++    }
+ done:
+     if (format)
+@@ -1185,7 +1190,10 @@ static void context_subscribe_callback(pa_context *c, pa_subscription_event_type
+ }
+ static void context_state_callback(pa_context *c, void *userdata) {
++    pa_operation *o = NULL;
++
+     pa_assert(c);
++
+     switch (pa_context_get_state(c)) {
+         case PA_CONTEXT_CONNECTING:
+         case PA_CONTEXT_AUTHORIZING:
+@@ -1195,21 +1203,26 @@ static void context_state_callback(pa_context *c, void *userdata) {
+         case PA_CONTEXT_READY:
+             switch (action) {
+                 case STAT:
+-                    pa_operation_unref(pa_context_stat(c, stat_callback, NULL));
++                    o = pa_context_stat(c, stat_callback, NULL);
+                     if (short_list_format)
+                         break;
+-                    actions++;
++
++                    if (o) {
++                        pa_operation_unref(o);
++                        actions++;
++                    }
++                    /* Fall through */
+                 case INFO:
+-                    pa_operation_unref(pa_context_get_server_info(c, get_server_info_callback, NULL));
++                    o = pa_context_get_server_info(c, get_server_info_callback, NULL);
+                     break;
+                 case PLAY_SAMPLE:
+-                    pa_operation_unref(pa_context_play_sample(c, sample_name, sink_name, PA_VOLUME_NORM, simple_callback, NULL));
++                    o = pa_context_play_sample(c, sample_name, sink_name, PA_VOLUME_NORM, simple_callback, NULL);
+                     break;
+                 case REMOVE_SAMPLE:
+-                    pa_operation_unref(pa_context_remove_sample(c, sample_name, simple_callback, NULL));
++                    o = pa_context_remove_sample(c, sample_name, simple_callback, NULL);
+                     break;
+                 case UPLOAD_SAMPLE:
+@@ -1219,165 +1232,205 @@ static void context_state_callback(pa_context *c, void *userdata) {
+                     pa_stream_set_state_callback(sample_stream, stream_state_callback, NULL);
+                     pa_stream_set_write_callback(sample_stream, stream_write_callback, NULL);
+                     pa_stream_connect_upload(sample_stream, sample_length);
++                    actions++;
+                     break;
+                 case EXIT:
+-                    pa_operation_unref(pa_context_exit_daemon(c, simple_callback, NULL));
++                    o = pa_context_exit_daemon(c, simple_callback, NULL);
+                     break;
+                 case LIST:
+                     if (list_type) {
+                         if (pa_streq(list_type, "modules"))
+-                            pa_operation_unref(pa_context_get_module_info_list(c, get_module_info_callback, NULL));
++                            o = pa_context_get_module_info_list(c, get_module_info_callback, NULL);
+                         else if (pa_streq(list_type, "sinks"))
+-                            pa_operation_unref(pa_context_get_sink_info_list(c, get_sink_info_callback, NULL));
++                            o = pa_context_get_sink_info_list(c, get_sink_info_callback, NULL);
+                         else if (pa_streq(list_type, "sources"))
+-                            pa_operation_unref(pa_context_get_source_info_list(c, get_source_info_callback, NULL));
++                            o = pa_context_get_source_info_list(c, get_source_info_callback, NULL);
+                         else if (pa_streq(list_type, "sink-inputs"))
+-                            pa_operation_unref(pa_context_get_sink_input_info_list(c, get_sink_input_info_callback, NULL));
++                            o = pa_context_get_sink_input_info_list(c, get_sink_input_info_callback, NULL);
+                         else if (pa_streq(list_type, "source-outputs"))
+-                            pa_operation_unref(pa_context_get_source_output_info_list(c, get_source_output_info_callback, NULL));
++                            o = pa_context_get_source_output_info_list(c, get_source_output_info_callback, NULL);
+                         else if (pa_streq(list_type, "clients"))
+-                            pa_operation_unref(pa_context_get_client_info_list(c, get_client_info_callback, NULL));
++                            o = pa_context_get_client_info_list(c, get_client_info_callback, NULL);
+                         else if (pa_streq(list_type, "samples"))
+-                            pa_operation_unref(pa_context_get_sample_info_list(c, get_sample_info_callback, NULL));
++                            o = pa_context_get_sample_info_list(c, get_sample_info_callback, NULL);
+                         else if (pa_streq(list_type, "cards"))
+-                            pa_operation_unref(pa_context_get_card_info_list(c, get_card_info_callback, NULL));
++                            o = pa_context_get_card_info_list(c, get_card_info_callback, NULL);
+                       else if (pa_streq(list_type, "nodes"))
+-                          pa_operation_unref(pa_ext_node_manager_read_nodes(c, node_list_callback, NULL));
++                          o = pa_ext_node_manager_read_nodes(c, node_list_callback, NULL);
+                         else
+                             pa_assert_not_reached();
+                     } else {
+-                        actions = 8;
+-                        pa_operation_unref(pa_context_get_module_info_list(c, get_module_info_callback, NULL));
+-                        pa_operation_unref(pa_context_get_sink_info_list(c, get_sink_info_callback, NULL));
+-                        pa_operation_unref(pa_context_get_source_info_list(c, get_source_info_callback, NULL));
+-                        pa_operation_unref(pa_context_get_sink_input_info_list(c, get_sink_input_info_callback, NULL));
+-                        pa_operation_unref(pa_context_get_source_output_info_list(c, get_source_output_info_callback, NULL));
+-                        pa_operation_unref(pa_context_get_client_info_list(c, get_client_info_callback, NULL));
+-                        pa_operation_unref(pa_context_get_sample_info_list(c, get_sample_info_callback, NULL));
+-                        pa_operation_unref(pa_context_get_card_info_list(c, get_card_info_callback, NULL));
++                        o = pa_context_get_module_info_list(c, get_module_info_callback, NULL);
++                        if (o) {
++                            pa_operation_unref(o);
++                            actions++;
++                        }
++
++                        o = pa_context_get_sink_info_list(c, get_sink_info_callback, NULL);
++                        if (o) {
++                            pa_operation_unref(o);
++                            actions++;
++                        }
++
++                        o = pa_context_get_source_info_list(c, get_source_info_callback, NULL);
++                        if (o) {
++                            pa_operation_unref(o);
++                            actions++;
++                        }
++                        o = pa_context_get_sink_input_info_list(c, get_sink_input_info_callback, NULL);
++                        if (o) {
++                            pa_operation_unref(o);
++                            actions++;
++                        }
++
++                        o = pa_context_get_source_output_info_list(c, get_source_output_info_callback, NULL);
++                        if (o) {
++                            pa_operation_unref(o);
++                            actions++;
++                        }
++
++                        o = pa_context_get_client_info_list(c, get_client_info_callback, NULL);
++                        if (o) {
++                            pa_operation_unref(o);
++                            actions++;
++                        }
++
++                        o = pa_context_get_sample_info_list(c, get_sample_info_callback, NULL);
++                        if (o) {
++                            pa_operation_unref(o);
++                            actions++;
++                        }
++
++                        o = pa_context_get_card_info_list(c, get_card_info_callback, NULL);
++                        if (o) {
++                            pa_operation_unref(o);
++                            actions++;
++                        }
++
++                        o = NULL;
+                     }
+                     break;
+                 case MOVE_SINK_INPUT:
+-                    pa_operation_unref(pa_context_move_sink_input_by_name(c, sink_input_idx, sink_name, simple_callback, NULL));
++                    o = pa_context_move_sink_input_by_name(c, sink_input_idx, sink_name, simple_callback, NULL);
+                     break;
+                 case MOVE_SOURCE_OUTPUT:
+-                    pa_operation_unref(pa_context_move_source_output_by_name(c, source_output_idx, source_name, simple_callback, NULL));
++                    o = pa_context_move_source_output_by_name(c, source_output_idx, source_name, simple_callback, NULL);
+                     break;
+                 case LOAD_MODULE:
+-                    pa_operation_unref(pa_context_load_module(c, module_name, module_args, index_callback, NULL));
++                    o = pa_context_load_module(c, module_name, module_args, index_callback, NULL);
+                     break;
+                 case UNLOAD_MODULE:
+                     if (module_name)
+-                        pa_operation_unref(pa_context_get_module_info_list(c, unload_module_by_name_callback, NULL));
++                        o = pa_context_get_module_info_list(c, unload_module_by_name_callback, NULL);
+                     else
+-                        pa_operation_unref(pa_context_unload_module(c, module_index, simple_callback, NULL));
++                        o = pa_context_unload_module(c, module_index, simple_callback, NULL);
+                     break;
+                 case SUSPEND_SINK:
+                     if (sink_name)
+-                        pa_operation_unref(pa_context_suspend_sink_by_name(c, sink_name, suspend, simple_callback, NULL));
++                        o = pa_context_suspend_sink_by_name(c, sink_name, suspend, simple_callback, NULL);
+                     else
+-                        pa_operation_unref(pa_context_suspend_sink_by_index(c, PA_INVALID_INDEX, suspend, simple_callback, NULL));
++                        o = pa_context_suspend_sink_by_index(c, PA_INVALID_INDEX, suspend, simple_callback, NULL);
+                     break;
+                 case SUSPEND_SOURCE:
+                     if (source_name)
+-                        pa_operation_unref(pa_context_suspend_source_by_name(c, source_name, suspend, simple_callback, NULL));
++                        o = pa_context_suspend_source_by_name(c, source_name, suspend, simple_callback, NULL);
+                     else
+-                        pa_operation_unref(pa_context_suspend_source_by_index(c, PA_INVALID_INDEX, suspend, simple_callback, NULL));
++                        o = pa_context_suspend_source_by_index(c, PA_INVALID_INDEX, suspend, simple_callback, NULL);
+                     break;
+                 case SET_CARD_PROFILE:
+-                    pa_operation_unref(pa_context_set_card_profile_by_name(c, card_name, profile_name, simple_callback, NULL));
++                    o = pa_context_set_card_profile_by_name(c, card_name, profile_name, simple_callback, NULL);
+                     break;
+                 case SET_SINK_PORT:
+-                    pa_operation_unref(pa_context_set_sink_port_by_name(c, sink_name, port_name, simple_callback, NULL));
++                    o = pa_context_set_sink_port_by_name(c, sink_name, port_name, simple_callback, NULL);
+                     break;
+                 case SET_DEFAULT_SINK:
+-                    pa_operation_unref(pa_context_set_default_sink(c, sink_name, simple_callback, NULL));
++                    o = pa_context_set_default_sink(c, sink_name, simple_callback, NULL);
+                     break;
+                 case SET_SOURCE_PORT:
+-                    pa_operation_unref(pa_context_set_source_port_by_name(c, source_name, port_name, simple_callback, NULL));
++                    o = pa_context_set_source_port_by_name(c, source_name, port_name, simple_callback, NULL);
+                     break;
+                 case SET_DEFAULT_SOURCE:
+-                    pa_operation_unref(pa_context_set_default_source(c, source_name, simple_callback, NULL));
++                    o = pa_context_set_default_source(c, source_name, simple_callback, NULL);
+                     break;
+                 case SET_SINK_MUTE:
+                     if (mute == TOGGLE_MUTE)
+-                        pa_operation_unref(pa_context_get_sink_info_by_name(c, sink_name, sink_toggle_mute_callback, NULL));
++                        o = pa_context_get_sink_info_by_name(c, sink_name, sink_toggle_mute_callback, NULL);
+                     else
+-                        pa_operation_unref(pa_context_set_sink_mute_by_name(c, sink_name, mute, simple_callback, NULL));
++                        o = pa_context_set_sink_mute_by_name(c, sink_name, mute, simple_callback, NULL);
+                     break;
+                 case SET_SOURCE_MUTE:
+                     if (mute == TOGGLE_MUTE)
+-                        pa_operation_unref(pa_context_get_source_info_by_name(c, source_name, source_toggle_mute_callback, NULL));
++                        o = pa_context_get_source_info_by_name(c, source_name, source_toggle_mute_callback, NULL);
+                     else
+-                        pa_operation_unref(pa_context_set_source_mute_by_name(c, source_name, mute, simple_callback, NULL));
++                        o = pa_context_set_source_mute_by_name(c, source_name, mute, simple_callback, NULL);
+                     break;
+                 case SET_SINK_INPUT_MUTE:
+                     if (mute == TOGGLE_MUTE)
+-                        pa_operation_unref(pa_context_get_sink_input_info(c, sink_input_idx, sink_input_toggle_mute_callback, NULL));
++                        o = pa_context_get_sink_input_info(c, sink_input_idx, sink_input_toggle_mute_callback, NULL);
+                     else
+-                        pa_operation_unref(pa_context_set_sink_input_mute(c, sink_input_idx, mute, simple_callback, NULL));
++                        o = pa_context_set_sink_input_mute(c, sink_input_idx, mute, simple_callback, NULL);
+                     break;
+                 case SET_SOURCE_OUTPUT_MUTE:
+                     if (mute == TOGGLE_MUTE)
+-                        pa_operation_unref(pa_context_get_source_output_info(c, source_output_idx, source_output_toggle_mute_callback, NULL));
++                        o = pa_context_get_source_output_info(c, source_output_idx, source_output_toggle_mute_callback, NULL);
+                     else
+-                        pa_operation_unref(pa_context_set_source_output_mute(c, source_output_idx, mute, simple_callback, NULL));
++                        o = pa_context_set_source_output_mute(c, source_output_idx, mute, simple_callback, NULL);
+                     break;
+                 case SET_SINK_VOLUME:
+                     if ((volume_flags & VOL_RELATIVE) == VOL_RELATIVE) {
+-                        pa_operation_unref(pa_context_get_sink_info_by_name(c, sink_name, get_sink_volume_callback, NULL));
++                        o = pa_context_get_sink_info_by_name(c, sink_name, get_sink_volume_callback, NULL);
+                     } else {
+                         pa_cvolume v;
+                         pa_cvolume_set(&v, 1, volume);
+-                        pa_operation_unref(pa_context_set_sink_volume_by_name(c, sink_name, &v, simple_callback, NULL));
++                        o = pa_context_set_sink_volume_by_name(c, sink_name, &v, simple_callback, NULL);
+                     }
+                     break;
+                 case SET_SOURCE_VOLUME:
+                     if ((volume_flags & VOL_RELATIVE) == VOL_RELATIVE) {
+-                        pa_operation_unref(pa_context_get_source_info_by_name(c, source_name, get_source_volume_callback, NULL));
++                        o = pa_context_get_source_info_by_name(c, source_name, get_source_volume_callback, NULL);
+                     } else {
+                         pa_cvolume v;
+                         pa_cvolume_set(&v, 1, volume);
+-                        pa_operation_unref(pa_context_set_source_volume_by_name(c, source_name, &v, simple_callback, NULL));
++                        o = pa_context_set_source_volume_by_name(c, source_name, &v, simple_callback, NULL);
+                     }
+                     break;
+                 case SET_SINK_INPUT_VOLUME:
+                     if ((volume_flags & VOL_RELATIVE) == VOL_RELATIVE) {
+-                        pa_operation_unref(pa_context_get_sink_input_info(c, sink_input_idx, get_sink_input_volume_callback, NULL));
++                        o = pa_context_get_sink_input_info(c, sink_input_idx, get_sink_input_volume_callback, NULL);
+                     } else {
+                         pa_cvolume v;
+                         pa_cvolume_set(&v, 1, volume);
+-                        pa_operation_unref(pa_context_set_sink_input_volume(c, sink_input_idx, &v, simple_callback, NULL));
++                        o = pa_context_set_sink_input_volume(c, sink_input_idx, &v, simple_callback, NULL);
+                     }
+                     break;
+                 case SET_SOURCE_OUTPUT_VOLUME:
+                     if ((volume_flags & VOL_RELATIVE) == VOL_RELATIVE) {
+-                        pa_operation_unref(pa_context_get_source_output_info(c, source_output_idx, get_source_output_volume_callback, NULL));
++                        o = pa_context_get_source_output_info(c, source_output_idx, get_source_output_volume_callback, NULL);
+                     } else {
+                         pa_cvolume v;
+                         pa_cvolume_set(&v, 1, volume);
+-                        pa_operation_unref(pa_context_set_source_output_volume(c, source_output_idx, &v, simple_callback, NULL));
++                        o = pa_context_set_source_output_volume(c, source_output_idx, &v, simple_callback, NULL);
+                     }
+                     break;
+@@ -1386,25 +1439,24 @@ static void context_state_callback(pa_context *c, void *userdata) {
+                     break;
+                 case SET_PORT_LATENCY_OFFSET:
+-                    pa_operation_unref(pa_context_set_port_latency_offset(c, card_name, port_name, latency_offset, simple_callback, NULL));
++                    o = pa_context_set_port_latency_offset(c, card_name, port_name, latency_offset, simple_callback, NULL);
+                     break;
+                 case SUBSCRIBE:
+                     pa_context_set_subscribe_callback(c, context_subscribe_callback, NULL);
+-                    pa_operation_unref(pa_context_subscribe(
+-                                              c,
+-                                              PA_SUBSCRIPTION_MASK_SINK|
+-                                              PA_SUBSCRIPTION_MASK_SOURCE|
+-                                              PA_SUBSCRIPTION_MASK_SINK_INPUT|
+-                                              PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
+-                                              PA_SUBSCRIPTION_MASK_MODULE|
+-                                              PA_SUBSCRIPTION_MASK_CLIENT|
+-                                              PA_SUBSCRIPTION_MASK_SAMPLE_CACHE|
+-                                              PA_SUBSCRIPTION_MASK_SERVER|
+-                                              PA_SUBSCRIPTION_MASK_CARD,
+-                                              NULL,
+-                                              NULL));
++                    o = pa_context_subscribe(c,
++                                             PA_SUBSCRIPTION_MASK_SINK|
++                                             PA_SUBSCRIPTION_MASK_SOURCE|
++                                             PA_SUBSCRIPTION_MASK_SINK_INPUT|
++                                             PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
++                                             PA_SUBSCRIPTION_MASK_MODULE|
++                                             PA_SUBSCRIPTION_MASK_CLIENT|
++                                             PA_SUBSCRIPTION_MASK_SAMPLE_CACHE|
++                                             PA_SUBSCRIPTION_MASK_SERVER|
++                                             PA_SUBSCRIPTION_MASK_CARD,
++                                             NULL,
++                                             NULL);
+                     break;
+               case NODE_CONNECT:
+                   pa_operation_unref(pa_ext_node_manager_connect_nodes(c,
+@@ -1422,6 +1474,17 @@ static void context_state_callback(pa_context *c, void *userdata) {
+                 default:
+                     pa_assert_not_reached();
+             }
++
++            if (o) {
++                pa_operation_unref(o);
++                actions++;
++            }
++
++            if (actions == 0) {
++                pa_log("Operation failed: %s", pa_strerror(pa_context_errno(c)));
++                quit(1);
++            }
++
+             break;
+         case PA_CONTEXT_TERMINATED:
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0052-volume-Increase-PA_SW_VOLUME_SNPRINT_DB_MAX.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0052-volume-Increase-PA_SW_VOLUME_SNPRINT_DB_MAX.patch
new file mode 100644 (file)
index 0000000..32330d6
--- /dev/null
@@ -0,0 +1,26 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Sun, 9 Mar 2014 10:50:23 +0200
+Subject: volume: Increase PA_SW_VOLUME_SNPRINT_DB_MAX
+
+10 bytes isn't enough for "-123.45 dB", including the terminating null
+byte.
+
+Change-Id: I865060befd01d3dde69556c1f90b7de55350501a
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulse/volume.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/pulse/volume.h b/src/pulse/volume.h
+index 3ffb573..cfdc85c 100644
+--- a/src/pulse/volume.h
++++ b/src/pulse/volume.h
+@@ -216,7 +216,7 @@ char *pa_volume_snprint(char *s, size_t l, pa_volume_t v);
+  * any release without warning and without being considered API or ABI
+  * breakage. You should not use this definition anywhere where it
+  * might become part of an ABI. \since 0.9.15 */
+-#define PA_SW_VOLUME_SNPRINT_DB_MAX 10
++#define PA_SW_VOLUME_SNPRINT_DB_MAX 11
+ /** Pretty print a volume but show dB values. \since 0.9.15 */
+ char *pa_sw_volume_snprint_dB(char *s, size_t l, pa_volume_t v);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0053-sink-input-source-output-Fix-mute-saving.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0053-sink-input-source-output-Fix-mute-saving.patch
new file mode 100644 (file)
index 0000000..fc389f6
--- /dev/null
@@ -0,0 +1,37 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 14 Apr 2014 14:29:48 +0300
+Subject: sink-input, source-output: Fix mute saving
+
+Change-Id: I2298ab51a384c3ddfa33da3e941e03f5027b4d77
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/sink-input.c    | 2 +-
+ src/pulsecore/source-output.c | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index dff6324..d0509b3 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -1470,7 +1470,7 @@ void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save) {
+     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+     if (!i->muted == !mute) {
+-        i->save_muted = i->save_muted || mute;
++        i->save_muted |= save;
+         return;
+     }
+diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
+index 4e4b7e9..b12758a 100644
+--- a/src/pulsecore/source-output.c
++++ b/src/pulsecore/source-output.c
+@@ -1062,7 +1062,7 @@ void pa_source_output_set_mute(pa_source_output *o, bool mute, bool save) {
+     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+     if (!o->muted == !mute) {
+-        o->save_muted = o->save_muted || mute;
++        o->save_muted |= save;
+         return;
+     }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0054-direction-Add-a-couple-of-direction-helper-functions.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0054-direction-Add-a-couple-of-direction-helper-functions.patch
new file mode 100644 (file)
index 0000000..3282596
--- /dev/null
@@ -0,0 +1,180 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Wed, 26 Mar 2014 10:25:17 +0200
+Subject: direction: Add a couple of direction helper functions
+
+Change-Id: I365acd7ce3e7abcbcb8a532c79016fca558238e8
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ doxygen/doxygen.conf.in |  1 +
+ src/Makefile.am         |  2 ++
+ src/map-file            |  2 ++
+ src/pulse/direction.c   | 46 ++++++++++++++++++++++++++++++++++++++++++++++
+ src/pulse/direction.h   | 37 +++++++++++++++++++++++++++++++++++++
+ src/pulse/pulseaudio.h  |  5 +++--
+ 6 files changed, 91 insertions(+), 2 deletions(-)
+ create mode 100644 src/pulse/direction.c
+ create mode 100644 src/pulse/direction.h
+
+diff --git a/doxygen/doxygen.conf.in b/doxygen/doxygen.conf.in
+index a078e27..40cea8b 100644
+--- a/doxygen/doxygen.conf.in
++++ b/doxygen/doxygen.conf.in
+@@ -421,6 +421,7 @@ INPUT = \
+     @srcdir@/../src/pulse/channelmap.h \
+     @srcdir@/../src/pulse/context.h \
+     @srcdir@/../src/pulse/def.h \
++    @srcdir@/../src/pulse/direction.h \
+     @srcdir@/../src/pulse/error.h \
+     @srcdir@/../src/pulse/ext-stream-restore.h \
+     @srcdir@/../src/pulse/ext-device-manager.h \
+diff --git a/src/Makefile.am b/src/Makefile.am
+index b09903f..fe6cc53 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -758,6 +758,7 @@ pulseinclude_HEADERS = \
+               pulse/channelmap.h \
+               pulse/context.h \
+               pulse/def.h \
++              pulse/direction.h \
+               pulse/error.h \
+               pulse/ext-device-manager.h \
+               pulse/ext-device-restore.h \
+@@ -810,6 +811,7 @@ libpulse_la_SOURCES = \
+               pulse/channelmap.c pulse/channelmap.h \
+               pulse/context.c pulse/context.h \
+               pulse/def.h \
++              pulse/direction.c pulse/direction.h \
+               pulse/error.c pulse/error.h \
+               pulse/ext-device-manager.c pulse/ext-device-manager.h \
+               pulse/ext-device-restore.c pulse/ext-device-restore.h \
+diff --git a/src/map-file b/src/map-file
+index 20e577a..fbf3f22 100644
+--- a/src/map-file
++++ b/src/map-file
+@@ -153,6 +153,8 @@ pa_cvolume_set_position;
+ pa_cvolume_snprint;
+ pa_cvolume_snprint_verbose;
+ pa_cvolume_valid;
++pa_direction_to_string;
++pa_direction_valid;
+ pa_encoding_to_string;
+ pa_ext_device_manager_delete;
+ pa_ext_device_manager_enable_role_device_priority_routing;
+diff --git a/src/pulse/direction.c b/src/pulse/direction.c
+new file mode 100644
+index 0000000..95f5e00
+--- /dev/null
++++ b/src/pulse/direction.c
+@@ -0,0 +1,46 @@
++/***
++  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 "direction.h"
++
++#include <pulsecore/i18n.h>
++
++int pa_direction_valid(pa_direction_t direction) {
++    if (direction != PA_DIRECTION_INPUT
++            && direction != PA_DIRECTION_OUTPUT
++            && direction != (PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT))
++        return 0;
++
++    return 1;
++}
++
++const char *pa_direction_to_string(pa_direction_t direction) {
++    pa_init_i18n();
++
++    if (direction == PA_DIRECTION_INPUT)
++        return _("input");
++    if (direction == PA_DIRECTION_OUTPUT)
++        return _("output");
++    if (direction == (PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT))
++        return _("bidirectional");
++
++    return _("invalid");
++}
+diff --git a/src/pulse/direction.h b/src/pulse/direction.h
+new file mode 100644
+index 0000000..127f07a
+--- /dev/null
++++ b/src/pulse/direction.h
+@@ -0,0 +1,37 @@
++#ifndef foodirectionhfoo
++#define foodirectionhfoo
++
++/***
++  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/def.h>
++
++/** \file
++ * Utility functions for \ref pa_direction_t. */
++
++/** Return non-zero if the given value is a valid direction (either input,
++ * output or bidirectional). \since 6.0 */
++int pa_direction_valid(pa_direction_t direction) PA_GCC_CONST;
++
++/** Return a textual representation of the direction. \since 6.0 */
++const char *pa_direction_to_string(pa_direction_t direction);
++
++#endif
+diff --git a/src/pulse/pulseaudio.h b/src/pulse/pulseaudio.h
+index 21b7213..2e270dd 100644
+--- a/src/pulse/pulseaudio.h
++++ b/src/pulse/pulseaudio.h
+@@ -23,6 +23,7 @@
+   USA.
+ ***/
++#include <pulse/direction.h>
+ #include <pulse/mainloop-api.h>
+ #include <pulse/sample.h>
+ #include <pulse/format.h>
+@@ -49,8 +50,8 @@
+ /** \file
+  * Include all libpulse header files at once. The following files are
+- * included: \ref mainloop-api.h, \ref sample.h, \ref def.h, \ref
+- * context.h, \ref stream.h, \ref introspect.h, \ref subscribe.h, \ref
++ * included: \ref direction.h, \ref mainloop-api.h, \ref sample.h, \ref def.h,
++ * \ref context.h, \ref stream.h, \ref introspect.h, \ref subscribe.h, \ref
+  * scache.h, \ref version.h, \ref error.h, \ref channelmap.h, \ref
+  * operation.h,\ref volume.h, \ref xmalloc.h, \ref utf8.h, \ref
+  * thread-mainloop.h, \ref mainloop.h, \ref util.h, \ref proplist.h,
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0055-core-util-Add-pa_join.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0055-core-util-Add-pa_join.patch
new file mode 100644 (file)
index 0000000..d510f44
--- /dev/null
@@ -0,0 +1,52 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Wed, 26 Mar 2014 13:15:12 +0200
+Subject: core-util: Add pa_join()
+
+Change-Id: I84ac0ee7a3097fce8ed9bad26b210fc97db9e9a7
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/core-util.c | 18 ++++++++++++++++++
+ src/pulsecore/core-util.h |  1 +
+ 2 files changed, 19 insertions(+)
+
+diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c
+index 1256a1e..508b3d3 100644
+--- a/src/pulsecore/core-util.c
++++ b/src/pulsecore/core-util.c
+@@ -1120,6 +1120,24 @@ char *pa_split_spaces(const char *c, const char **state) {
+     return pa_xstrndup(current, l);
+ }
++char *pa_join(const char * const *strings, unsigned n_strings, const char *delimiter) {
++    pa_strbuf *buf;
++    unsigned i;
++
++    pa_assert(strings || n_strings == 0);
++
++    buf = pa_strbuf_new();
++
++    for (i = 0; i < n_strings; i++) {
++        if (i > 0 && delimiter)
++            pa_strbuf_puts(buf, delimiter);
++
++        pa_strbuf_puts(buf, strings[i]);
++    }
++
++    return pa_strbuf_tostring_free(buf);
++}
++
+ PA_STATIC_TLS_DECLARE(signame, pa_xfree);
+ /* Return the name of an UNIX signal. Similar to Solaris sig2str() */
+diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
+index e6cb261..aba1863 100644
+--- a/src/pulsecore/core-util.h
++++ b/src/pulsecore/core-util.h
+@@ -108,6 +108,7 @@ static inline const char *pa_strna(const char *x) {
+ char *pa_split(const char *c, const char*delimiters, const char **state);
+ const char *pa_split_in_place(const char *c, const char*delimiters, int *n, const char **state);
+ char *pa_split_spaces(const char *c, const char **state);
++char *pa_join(const char * const *strings, unsigned n_strings, const char *delimiter);
+ char *pa_strip_nl(char *s);
+ char *pa_strip(char *s);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0056-dynarray-Add-pa_dynarray_get_raw_array.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0056-dynarray-Add-pa_dynarray_get_raw_array.patch
new file mode 100644 (file)
index 0000000..cf2c694
--- /dev/null
@@ -0,0 +1,36 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Wed, 26 Mar 2014 13:33:31 +0200
+Subject: dynarray: Add pa_dynarray_get_raw_array()
+
+Change-Id: I6e40c2a20586d13c99c9d98059e4dbb1d0e9e562
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/dynarray.c | 6 ++++++
+ src/pulsecore/dynarray.h | 1 +
+ 2 files changed, 7 insertions(+)
+
+diff --git a/src/pulsecore/dynarray.c b/src/pulsecore/dynarray.c
+index b207eca..b65fb62 100644
+--- a/src/pulsecore/dynarray.c
++++ b/src/pulsecore/dynarray.c
+@@ -93,3 +93,9 @@ unsigned pa_dynarray_size(pa_dynarray *array) {
+     return array->n_entries;
+ }
++
++void * const *pa_dynarray_get_raw_array(pa_dynarray *array) {
++    pa_assert(array);
++
++    return array->data;
++}
+diff --git a/src/pulsecore/dynarray.h b/src/pulsecore/dynarray.h
+index 04dd2d2..078acec 100644
+--- a/src/pulsecore/dynarray.h
++++ b/src/pulsecore/dynarray.h
+@@ -54,5 +54,6 @@ void *pa_dynarray_get(pa_dynarray *array, unsigned i);
+ void *pa_dynarray_steal_last(pa_dynarray *array);
+ unsigned pa_dynarray_size(pa_dynarray *array);
++void * const *pa_dynarray_get_raw_array(pa_dynarray *array);
+ #endif
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0057-dynarray-Add-PA_DYNARRAY_FOREACH.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0057-dynarray-Add-PA_DYNARRAY_FOREACH.patch
new file mode 100644 (file)
index 0000000..64d8ce5
--- /dev/null
@@ -0,0 +1,64 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Thu, 19 Dec 2013 21:29:50 +0200
+Subject: dynarray: Add PA_DYNARRAY_FOREACH
+
+The PA_DYNARRAY_FOREACH macro requires that pa_dynarray_get() returns
+NULL if the index is out of bounds.
+
+Change-Id: If9db312516fbb079e8b67d94d35a44783ab3395a
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/dynarray.c  | 4 +++-
+ src/pulsecore/dynarray.h  | 5 +++++
+ src/pulsecore/tokenizer.c | 3 ---
+ 3 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/src/pulsecore/dynarray.c b/src/pulsecore/dynarray.c
+index b65fb62..8dd8fab 100644
+--- a/src/pulsecore/dynarray.c
++++ b/src/pulsecore/dynarray.c
+@@ -74,7 +74,9 @@ void pa_dynarray_append(pa_dynarray *array, void *p) {
+ void *pa_dynarray_get(pa_dynarray *array, unsigned i) {
+     pa_assert(array);
+-    pa_assert(i < array->n_entries);
++
++    if (i >= array->n_entries)
++        return NULL;
+     return array->data[i];
+ }
+diff --git a/src/pulsecore/dynarray.h b/src/pulsecore/dynarray.h
+index 078acec..65030f2 100644
+--- a/src/pulsecore/dynarray.h
++++ b/src/pulsecore/dynarray.h
+@@ -48,6 +48,8 @@ pa_dynarray* pa_dynarray_new(pa_free_cb_t free_cb);
+ void pa_dynarray_free(pa_dynarray *array);
+ void pa_dynarray_append(pa_dynarray *array, void *p);
++
++/* Returns NULL if i is out of bounds. */
+ void *pa_dynarray_get(pa_dynarray *array, unsigned i);
+ /* Returns the removed item, or NULL if the array is empty. */
+@@ -56,4 +58,7 @@ void *pa_dynarray_steal_last(pa_dynarray *array);
+ unsigned pa_dynarray_size(pa_dynarray *array);
+ void * const *pa_dynarray_get_raw_array(pa_dynarray *array);
++#define PA_DYNARRAY_FOREACH(elem, array, idx) \
++    for ((idx) = 0; ((elem) = pa_dynarray_get(array, idx)); (idx)++)
++
+ #endif
+diff --git a/src/pulsecore/tokenizer.c b/src/pulsecore/tokenizer.c
+index 4c610e8..d71a7da 100644
+--- a/src/pulsecore/tokenizer.c
++++ b/src/pulsecore/tokenizer.c
+@@ -80,8 +80,5 @@ const char *pa_tokenizer_get(pa_tokenizer *t, unsigned i) {
+     pa_assert(a);
+-    if (i >= pa_dynarray_size(a))
+-        return NULL;
+-
+     return pa_dynarray_get(a, i);
+ }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0058-dynarray-Add-pa_dynarray_remove_last.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0058-dynarray-Add-pa_dynarray_remove_last.patch
new file mode 100644 (file)
index 0000000..a6e0f56
--- /dev/null
@@ -0,0 +1,51 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Wed, 21 May 2014 11:32:09 +0300
+Subject: dynarray: Add pa_dynarray_remove_last()
+
+Change-Id: I9098df96aac57a3ee2084061aa174f7ee02b1588
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/dynarray.c | 15 +++++++++++++++
+ src/pulsecore/dynarray.h |  3 +++
+ 2 files changed, 18 insertions(+)
+
+diff --git a/src/pulsecore/dynarray.c b/src/pulsecore/dynarray.c
+index 8dd8fab..c0b5fb0 100644
+--- a/src/pulsecore/dynarray.c
++++ b/src/pulsecore/dynarray.c
+@@ -81,6 +81,21 @@ void *pa_dynarray_get(pa_dynarray *array, unsigned i) {
+     return array->data[i];
+ }
++int pa_dynarray_remove_last(pa_dynarray *array) {
++    void *entry;
++
++    pa_assert(array);
++
++    entry = pa_dynarray_steal_last(array);
++    if (!entry)
++        return -PA_ERR_NOENTITY;
++
++    if (array->free_cb)
++        array->free_cb(entry);
++
++    return 0;
++}
++
+ void *pa_dynarray_steal_last(pa_dynarray *array) {
+     pa_assert(array);
+diff --git a/src/pulsecore/dynarray.h b/src/pulsecore/dynarray.h
+index 65030f2..fd91433 100644
+--- a/src/pulsecore/dynarray.h
++++ b/src/pulsecore/dynarray.h
+@@ -52,6 +52,9 @@ void pa_dynarray_append(pa_dynarray *array, void *p);
+ /* Returns NULL if i is out of bounds. */
+ void *pa_dynarray_get(pa_dynarray *array, unsigned i);
++/* Returns -PA_ERR_NOENTITY if the array is empty, and zero otherwise. */
++int pa_dynarray_remove_last(pa_dynarray *array);
++
+ /* Returns the removed item, or NULL if the array is empty. */
+ void *pa_dynarray_steal_last(pa_dynarray *array);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0059-dynarray-Add-pa_dynarray_remove_all.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0059-dynarray-Add-pa_dynarray_remove_all.patch
new file mode 100644 (file)
index 0000000..d2e66b5
--- /dev/null
@@ -0,0 +1,57 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Wed, 21 May 2014 11:36:19 +0300
+Subject: dynarray: Add pa_dynarray_remove_all()
+
+Change-Id: I35079f8fe4b361221a1bdc1fececbe318bf3ee0e
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/dynarray.c | 13 ++++++++-----
+ src/pulsecore/dynarray.h |  2 ++
+ 2 files changed, 10 insertions(+), 5 deletions(-)
+
+diff --git a/src/pulsecore/dynarray.c b/src/pulsecore/dynarray.c
+index c0b5fb0..6dba743 100644
+--- a/src/pulsecore/dynarray.c
++++ b/src/pulsecore/dynarray.c
+@@ -47,13 +47,9 @@ pa_dynarray* pa_dynarray_new(pa_free_cb_t free_cb) {
+ }
+ void pa_dynarray_free(pa_dynarray *array) {
+-    unsigned i;
+     pa_assert(array);
+-    if (array->free_cb)
+-        for (i = 0; i < array->n_entries; i++)
+-            array->free_cb(array->data[i]);
+-
++    pa_dynarray_remove_all(array);
+     pa_xfree(array->data);
+     pa_xfree(array);
+ }
+@@ -105,6 +101,13 @@ void *pa_dynarray_steal_last(pa_dynarray *array) {
+         return NULL;
+ }
++void pa_dynarray_remove_all(pa_dynarray *array) {
++    pa_assert(array);
++
++    while (array->n_entries > 0)
++        pa_dynarray_remove_last(array);
++}
++
+ unsigned pa_dynarray_size(pa_dynarray *array) {
+     pa_assert(array);
+diff --git a/src/pulsecore/dynarray.h b/src/pulsecore/dynarray.h
+index fd91433..439f181 100644
+--- a/src/pulsecore/dynarray.h
++++ b/src/pulsecore/dynarray.h
+@@ -58,6 +58,8 @@ int pa_dynarray_remove_last(pa_dynarray *array);
+ /* Returns the removed item, or NULL if the array is empty. */
+ void *pa_dynarray_steal_last(pa_dynarray *array);
++void pa_dynarray_remove_all(pa_dynarray *array);
++
+ unsigned pa_dynarray_size(pa_dynarray *array);
+ void * const *pa_dynarray_get_raw_array(pa_dynarray *array);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0060-hashmap-Add-pa_hashmap_remove_and_free.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0060-hashmap-Add-pa_hashmap_remove_and_free.patch
new file mode 100644 (file)
index 0000000..09ab108
--- /dev/null
@@ -0,0 +1,53 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Wed, 26 Mar 2014 13:58:40 +0200
+Subject: hashmap: Add pa_hashmap_remove_and_free()
+
+Change-Id: Ia3530c29cecf8964989aec4f0527fc982e80e34a
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/hashmap.c | 13 +++++++++++++
+ src/pulsecore/hashmap.h |  7 +++++++
+ 2 files changed, 20 insertions(+)
+
+diff --git a/src/pulsecore/hashmap.c b/src/pulsecore/hashmap.c
+index acac1e0..2cc03cb 100644
+--- a/src/pulsecore/hashmap.c
++++ b/src/pulsecore/hashmap.c
+@@ -207,6 +207,19 @@ void* pa_hashmap_remove(pa_hashmap *h, const void *key) {
+     return data;
+ }
++int pa_hashmap_remove_and_free(pa_hashmap *h, const void *key) {
++    void *data;
++
++    pa_assert(h);
++
++    data = pa_hashmap_remove(h, key);
++
++    if (data && h->value_free_func)
++        h->value_free_func(data);
++
++    return data ? 0 : -1;
++}
++
+ void pa_hashmap_remove_all(pa_hashmap *h) {
+     pa_assert(h);
+diff --git a/src/pulsecore/hashmap.h b/src/pulsecore/hashmap.h
+index e42732a..8042f7b 100644
+--- a/src/pulsecore/hashmap.h
++++ b/src/pulsecore/hashmap.h
+@@ -52,6 +52,13 @@ void* pa_hashmap_get(pa_hashmap *h, const void *key);
+ /* Returns the data of the entry while removing */
+ void* pa_hashmap_remove(pa_hashmap *h, const void *key);
++/* Removes the entry and frees the entry data. Returns a negative value if the
++ * entry is not found. FIXME: This function shouldn't be needed.
++ * pa_hashmap_remove() should free the entry data, and the current semantics of
++ * pa_hashmap_remove() should be implemented by a function called
++ * pa_hashmap_steal(). */
++int pa_hashmap_remove_and_free(pa_hashmap *h, const void *key);
++
+ /* Remove all entries but don't free the hashmap */
+ void pa_hashmap_remove_all(pa_hashmap *h);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0061-device-port-Add-pa_device_port.active.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0061-device-port-Add-pa_device_port.active.patch
new file mode 100644 (file)
index 0000000..ef71747
--- /dev/null
@@ -0,0 +1,236 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Wed, 26 Mar 2014 13:41:42 +0200
+Subject: device-port: Add pa_device_port.active
+
+In the Tizen volume API, I create and delete volume control objects
+for ports based on their state (only active ports have volume
+controls). Having the pa_device_port.active flag directly accessible
+is much nicer than figuring out the state by iterating through sinks
+and checking what their active port is. It's also safer to get a
+notification for a deactivated port before the port switch is
+executed, compared to using the SINK_PORT_CHANGED hook that is fired
+only after the port switch is complete.
+
+Change-Id: I3f7f8855721c8dc3a643708a72f6e35341ff7117
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/core.h        |  1 +
+ src/pulsecore/device-port.c | 15 +++++++++++++++
+ src/pulsecore/device-port.h |  4 ++++
+ src/pulsecore/sink.c        | 28 ++++++++++++++++++++++++----
+ src/pulsecore/source.c      | 29 +++++++++++++++++++++++++----
+ 5 files changed, 69 insertions(+), 8 deletions(-)
+
+diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
+index f268e42..e1cd18f 100644
+--- a/src/pulsecore/core.h
++++ b/src/pulsecore/core.h
+@@ -119,6 +119,7 @@ typedef enum pa_core_hook {
+     PA_CORE_HOOK_CARD_PROFILE_ADDED,
+     PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED,
+     PA_CORE_HOOK_PORT_AVAILABLE_CHANGED,
++    PA_CORE_HOOK_PORT_ACTIVE_CHANGED,
+     PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED,
+     PA_CORE_HOOK_MAX
+ } pa_core_hook_t;
+diff --git a/src/pulsecore/device-port.c b/src/pulsecore/device-port.c
+index 0b65d5c..c183990 100644
+--- a/src/pulsecore/device-port.c
++++ b/src/pulsecore/device-port.c
+@@ -176,3 +176,18 @@ void pa_device_port_set_latency_offset(pa_device_port *p, int64_t offset) {
+     pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, p->card->index);
+     pa_hook_fire(&core->hooks[PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED], p);
+ }
++
++void pa_device_port_active_changed(pa_device_port *port, bool new_active) {
++    bool old_active;
++
++    pa_assert(port);
++
++    old_active = port->active;
++
++    if (new_active == old_active)
++        return;
++
++    port->active = new_active;
++    pa_log_debug("Port %s %s.", port->name, new_active ? "activated" : "deactivated");
++    pa_hook_fire(&port->core->hooks[PA_CORE_HOOK_PORT_ACTIVE_CHANGED], port);
++}
+diff --git a/src/pulsecore/device-port.h b/src/pulsecore/device-port.h
+index b10d554..2964900 100644
+--- a/src/pulsecore/device-port.h
++++ b/src/pulsecore/device-port.h
+@@ -48,6 +48,7 @@ struct pa_device_port {
+     unsigned priority;
+     pa_available_t available;         /* PA_AVAILABLE_UNKNOWN, PA_AVAILABLE_NO or PA_AVAILABLE_YES */
++    bool active;
+     pa_proplist *proplist;
+     pa_hashmap *profiles; /* Does not own the profiles */
+@@ -83,4 +84,7 @@ void pa_device_port_set_available(pa_device_port *p, pa_available_t available);
+ void pa_device_port_set_latency_offset(pa_device_port *p, int64_t offset);
++/* Called from sink.c and source.c only. */
++void pa_device_port_active_changed(pa_device_port *port, bool new_active);
++
+ #endif
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index 61656ab..11a6e77 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -669,6 +669,9 @@ void pa_sink_put(pa_sink* s) {
+     else
+         pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0);
++    if (s->active_port)
++        pa_device_port_active_changed(s->active_port, true);
++
+     pa_source_put(s->monitor_source);
+     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_NEW, s->index);
+@@ -696,6 +699,9 @@ void pa_sink_unlink(pa_sink* s) {
+     if (linked)
+         pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_UNLINK], s);
++    if (s->active_port)
++        pa_device_port_active_changed(s->active_port, false);
++
+     if (s->state != PA_SINK_UNLINKED)
+         pa_namereg_unregister(s->core, s->name);
+     pa_idxset_remove_by_data(s->core->sinks, s, NULL);
+@@ -3410,6 +3416,7 @@ size_t pa_sink_get_max_request(pa_sink *s) {
+ /* Called from main context */
+ int pa_sink_set_port(pa_sink *s, const char *name, bool save) {
+     pa_device_port *port;
++    pa_device_port *old_port;
+     int ret;
+     pa_sink_assert_ref(s);
+@@ -3426,11 +3433,15 @@ int pa_sink_set_port(pa_sink *s, const char *name, bool save) {
+     if (!(port = pa_hashmap_get(s->ports, name)))
+         return -PA_ERR_NOENTITY;
+-    if (s->active_port == port) {
++    old_port = s->active_port;
++
++    if (port == old_port) {
+         s->save_port = s->save_port || save;
+         return 0;
+     }
++    pa_device_port_active_changed(old_port, false);
++
+     if (s->flags & PA_SINK_DEFERRED_VOLUME) {
+         struct sink_message_set_port msg = { .port = port, .ret = 0 };
+         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_PORT, &msg, 0, NULL) == 0);
+@@ -3439,17 +3450,26 @@ int pa_sink_set_port(pa_sink *s, const char *name, bool save) {
+     else
+         ret = s->set_port(s, port);
+-    if (ret < 0)
+-        return -PA_ERR_NOENTITY;
++    if (ret < 0) {
++        pa_log("Failed to set the port of sink %s from %s to %s.", s->name, old_port->name, port->name);
++
++        /* We don't know the real state of the device, but let's assume that
++         * the old port is still active, because s->active_port is left to
++         * point to the old port anyway. */
++        pa_device_port_active_changed(old_port, true);
++
++        return ret;
++    }
+     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+-    pa_log_info("Changed port of sink %u \"%s\" to %s", s->index, s->name, port->name);
++    pa_log_info("Changed port of sink %u \"%s\" from %s to %s", s->index, s->name, old_port->name, port->name);
+     s->active_port = port;
+     s->save_port = save;
+     pa_sink_set_latency_offset(s, s->active_port->latency_offset);
++    pa_device_port_active_changed(port, true);
+     pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], s);
+diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
+index af4c6ec..d39193f 100644
+--- a/src/pulsecore/source.c
++++ b/src/pulsecore/source.c
+@@ -611,6 +611,9 @@ void pa_source_put(pa_source *s) {
+     else
+         pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0);
++    if (s->active_port)
++        pa_device_port_active_changed(s->active_port, true);
++
+     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index);
+     pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PUT], s);
+ }
+@@ -631,6 +634,9 @@ void pa_source_unlink(pa_source *s) {
+     if (linked)
+         pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], s);
++    if (s->active_port)
++        pa_device_port_active_changed(s->active_port, false);
++
+     if (s->state != PA_SOURCE_UNLINKED)
+         pa_namereg_unregister(s->core, s->name);
+     pa_idxset_remove_by_data(s->core->sources, s, NULL);
+@@ -2608,6 +2614,7 @@ size_t pa_source_get_max_rewind(pa_source *s) {
+ /* Called from main context */
+ int pa_source_set_port(pa_source *s, const char *name, bool save) {
+     pa_device_port *port;
++    pa_device_port *old_port;
+     int ret;
+     pa_source_assert_ref(s);
+@@ -2624,11 +2631,15 @@ int pa_source_set_port(pa_source *s, const char *name, bool save) {
+     if (!(port = pa_hashmap_get(s->ports, name)))
+         return -PA_ERR_NOENTITY;
+-    if (s->active_port == port) {
++    old_port = s->active_port;
++
++    if (port == old_port) {
+         s->save_port = s->save_port || save;
+         return 0;
+     }
++    pa_device_port_active_changed(old_port, false);
++
+     if (s->flags & PA_SOURCE_DEFERRED_VOLUME) {
+         struct source_message_set_port msg = { .port = port, .ret = 0 };
+         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_PORT, &msg, 0, NULL) == 0);
+@@ -2637,16 +2648,26 @@ int pa_source_set_port(pa_source *s, const char *name, bool save) {
+     else
+         ret = s->set_port(s, port);
+-    if (ret < 0)
+-        return -PA_ERR_NOENTITY;
++    if (ret < 0) {
++        pa_log("Failed to set the port of sink %s from %s to %s.", s->name, old_port->name, port->name);
++
++        /* We don't know the real state of the device, but let's assume that
++         * the old port is still active, because s->active_port is left to
++         * point to the old port anyway. */
++        pa_device_port_active_changed(old_port, true);
++
++        return ret;
++    }
+     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+-    pa_log_info("Changed port of source %u \"%s\" to %s", s->index, s->name, port->name);
++    pa_log_info("Changed port of source %u \"%s\" from %s to %s", s->index, s->name, old_port->name, port->name);
+     s->active_port = port;
+     s->save_port = save;
++    pa_device_port_active_changed(port, true);
++
+     pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED], s);
+     return 0;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0062-sink-source-Assign-to-reference_volume-from-only-one.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0062-sink-source-Assign-to-reference_volume-from-only-one.patch
new file mode 100644 (file)
index 0000000..2f5a35c
--- /dev/null
@@ -0,0 +1,246 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 7 Apr 2014 12:20:58 +0300
+Subject: sink, source: Assign to reference_volume from only one place
+
+Forcing all reference volume changes to go through
+set_reference_volume_direct() makes it easier to check where the
+reference volume is changed, and it also allows us to have only one
+place where notifications for changed reference volume are sent.
+
+Change-Id: I2e769b8a2b0d7031a02446dead8ca2e0c3402751
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/sink-input.c    | 23 ++++++++++-------------
+ src/pulsecore/sink.c          | 30 ++++++++++++++++++++++++++----
+ src/pulsecore/sink.h          |  7 +++++++
+ src/pulsecore/source-output.c | 23 ++++++++++-------------
+ src/pulsecore/source.c        | 30 ++++++++++++++++++++++++++----
+ src/pulsecore/source.h        |  7 +++++++
+ 6 files changed, 86 insertions(+), 34 deletions(-)
+
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index d0509b3..6596eeb 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -1716,6 +1716,7 @@ int pa_sink_input_start_move(pa_sink_input *i) {
+  * their volume - this function does all that by using recursion. */
+ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
+     pa_cvolume old_volume;
++    pa_cvolume new_volume;
+     pa_assert(i);
+     pa_assert(dest);
+@@ -1787,25 +1788,21 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
+          *         (sinks that use volume sharing should always have
+          *          soft_volume of 0 dB) */
+-        old_volume = i->origin_sink->reference_volume;
+-
+-        i->origin_sink->reference_volume = root_sink->reference_volume;
+-        pa_cvolume_remap(&i->origin_sink->reference_volume, &root_sink->channel_map, &i->origin_sink->channel_map);
++        new_volume = root_sink->reference_volume;
++        pa_cvolume_remap(&new_volume, &root_sink->channel_map, &i->origin_sink->channel_map);
++        pa_sink_set_reference_volume_direct(i->origin_sink, &new_volume);
+         i->origin_sink->real_volume = root_sink->real_volume;
+         pa_cvolume_remap(&i->origin_sink->real_volume, &root_sink->channel_map, &i->origin_sink->channel_map);
+         pa_assert(pa_cvolume_is_norm(&i->origin_sink->soft_volume));
+-        /* Notify others about the changed sink volume. If you wonder whether
+-         * i->origin_sink->set_volume() should be called somewhere, that's not
+-         * the case, because sinks that use volume sharing shouldn't have any
+-         * internal volume that set_volume() would update. If you wonder
+-         * whether the thread_info variables should be synced, yes, they
+-         * should, and it's done by the PA_SINK_MESSAGE_FINISH_MOVE message
+-         * handler. */
+-        if (!pa_cvolume_equal(&i->origin_sink->reference_volume, &old_volume))
+-            pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, i->origin_sink->index);
++        /* If you wonder whether i->origin_sink->set_volume() should be called
++         * somewhere, that's not the case, because sinks that use volume
++         * sharing shouldn't have any internal volume that set_volume() would
++         * update. If you wonder whether the thread_info variables should be
++         * synced, yes, they should, and it's done by the
++         * PA_SINK_MESSAGE_FINISH_MOVE message handler. */
+         /* Recursively update origin sink inputs. */
+         PA_IDXSET_FOREACH(origin_sink_input, i->origin_sink->inputs, idx)
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index 11a6e77..e00dce5 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -2017,13 +2017,11 @@ static bool update_reference_volume(pa_sink *s, const pa_cvolume *v, const pa_ch
+     pa_cvolume_remap(&volume, channel_map, &s->channel_map);
+     reference_volume_changed = !pa_cvolume_equal(&volume, &s->reference_volume);
+-    s->reference_volume = volume;
++    pa_sink_set_reference_volume_direct(s, &volume);
+     s->save_volume = (!reference_volume_changed && s->save_volume) || save;
+-    if (reference_volume_changed)
+-        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+-    else if (!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
++    if (!reference_volume_changed && !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+         /* If the root sink's volume doesn't change, then there can't be any
+          * changes in the other sinks in the sink tree either.
+          *
+@@ -3905,3 +3903,27 @@ done:
+     return out_formats;
+ }
++
++/* Called from the main thread. */
++void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume) {
++    pa_cvolume old_volume;
++    char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++    char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++
++    pa_assert(s);
++    pa_assert(volume);
++
++    old_volume = s->reference_volume;
++
++    if (pa_cvolume_equal(volume, &old_volume))
++        return;
++
++    s->reference_volume = *volume;
++    pa_log_debug("The reference volume of sink %s changed from %s to %s.", s->name,
++                 pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &s->channel_map,
++                                            s->flags & PA_SINK_DECIBEL_VOLUME),
++                 pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &s->channel_map,
++                                            s->flags & PA_SINK_DECIBEL_VOLUME));
++
++    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
++}
+diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
+index 576f34b..41a439b 100644
+--- a/src/pulsecore/sink.h
++++ b/src/pulsecore/sink.h
+@@ -512,6 +512,13 @@ void pa_sink_invalidate_requested_latency(pa_sink *s, bool dynamic);
+ pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s);
++/* Called from the main thread, from sink-input.c only. The normal way to set
++ * the sink reference volume is to call pa_sink_set_volume(), but the flat
++ * volume logic in sink-input.c needs also a function that doesn't do all the
++ * extra stuff that pa_sink_set_volume() does. This function simply sets
++ * s->reference_volume and fires change notifications. */
++void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume);
++
+ /* Verify that we called in IO context (aka 'thread context), or that
+  * the sink is not yet set up, i.e. the thread not set up yet. See
+  * pa_assert_io_context() in thread-mq.h for more information. */
+diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
+index b12758a..169d98d 100644
+--- a/src/pulsecore/source-output.c
++++ b/src/pulsecore/source-output.c
+@@ -1263,6 +1263,7 @@ int pa_source_output_start_move(pa_source_output *o) {
+  * their volume - this function does all that by using recursion. */
+ static void update_volume_due_to_moving(pa_source_output *o, pa_source *dest) {
+     pa_cvolume old_volume;
++    pa_cvolume new_volume;
+     pa_assert(o);
+     pa_assert(dest);
+@@ -1336,25 +1337,21 @@ static void update_volume_due_to_moving(pa_source_output *o, pa_source *dest) {
+          *         (sources that use volume sharing should always have
+          *          soft_volume of 0 dB) */
+-        old_volume = o->destination_source->reference_volume;
+-
+-        o->destination_source->reference_volume = root_source->reference_volume;
+-        pa_cvolume_remap(&o->destination_source->reference_volume, &root_source->channel_map, &o->destination_source->channel_map);
++        new_volume = root_source->reference_volume;
++        pa_cvolume_remap(&new_volume, &root_source->channel_map, &o->destination_source->channel_map);
++        pa_source_set_reference_volume_direct(o->destination_source, &new_volume);
+         o->destination_source->real_volume = root_source->real_volume;
+         pa_cvolume_remap(&o->destination_source->real_volume, &root_source->channel_map, &o->destination_source->channel_map);
+         pa_assert(pa_cvolume_is_norm(&o->destination_source->soft_volume));
+-        /* Notify others about the changed source volume. If you wonder whether
+-         * o->destination_source->set_volume() should be called somewhere, that's not
+-         * the case, because sources that use volume sharing shouldn't have any
+-         * internal volume that set_volume() would update. If you wonder
+-         * whether the thread_info variables should be synced, yes, they
+-         * should, and it's done by the PA_SOURCE_MESSAGE_FINISH_MOVE message
+-         * handler. */
+-        if (!pa_cvolume_equal(&o->destination_source->reference_volume, &old_volume))
+-            pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, o->destination_source->index);
++        /* If you wonder whether o->destination_source->set_volume() should be
++         * called somewhere, that's not the case, because sources that use
++         * volume sharing shouldn't have any internal volume that set_volume()
++         * would update. If you wonder whether the thread_info variables should
++         * be synced, yes, they should, and it's done by the
++         * PA_SOURCE_MESSAGE_FINISH_MOVE message handler. */
+         /* Recursively update origin source outputs. */
+         PA_IDXSET_FOREACH(destination_source_output, o->destination_source->outputs, idx)
+diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
+index d39193f..f58bb44 100644
+--- a/src/pulsecore/source.c
++++ b/src/pulsecore/source.c
+@@ -1562,13 +1562,11 @@ static bool update_reference_volume(pa_source *s, const pa_cvolume *v, const pa_
+     pa_cvolume_remap(&volume, channel_map, &s->channel_map);
+     reference_volume_changed = !pa_cvolume_equal(&volume, &s->reference_volume);
+-    s->reference_volume = volume;
++    pa_source_set_reference_volume_direct(s, &volume);
+     s->save_volume = (!reference_volume_changed && s->save_volume) || save;
+-    if (reference_volume_changed)
+-        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+-    else if (!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
++    if (!reference_volume_changed && !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+         /* If the root source's volume doesn't change, then there can't be any
+          * changes in the other source in the source tree either.
+          *
+@@ -2899,3 +2897,27 @@ done:
+     return out_formats;
+ }
++
++/* Called from the main thread. */
++void pa_source_set_reference_volume_direct(pa_source *s, const pa_cvolume *volume) {
++    pa_cvolume old_volume;
++    char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++    char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++
++    pa_assert(s);
++    pa_assert(volume);
++
++    old_volume = s->reference_volume;
++
++    if (pa_cvolume_equal(volume, &old_volume))
++        return;
++
++    s->reference_volume = *volume;
++    pa_log_debug("The reference volume of source %s changed from %s to %s.", s->name,
++                 pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &s->channel_map,
++                                            s->flags & PA_SOURCE_DECIBEL_VOLUME),
++                 pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &s->channel_map,
++                                            s->flags & PA_SOURCE_DECIBEL_VOLUME));
++
++    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
++}
+diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
+index 5c74a51..6318595 100644
+--- a/src/pulsecore/source.h
++++ b/src/pulsecore/source.h
+@@ -426,6 +426,13 @@ bool pa_source_volume_change_apply(pa_source *s, pa_usec_t *usec_to_next);
+ void pa_source_invalidate_requested_latency(pa_source *s, bool dynamic);
+ pa_usec_t pa_source_get_latency_within_thread(pa_source *s);
++/* Called from the main thread, from source-output.c only. The normal way to
++ * set the source reference volume is to call pa_source_set_volume(), but the
++ * flat volume logic in source-output.c needs also a function that doesn't do
++ * all the extra stuff that pa_source_set_volume() does. This function simply
++ * sets s->reference_volume and fires change notifications. */
++void pa_source_set_reference_volume_direct(pa_source *s, const pa_cvolume *volume);
++
+ #define pa_source_assert_io_context(s) \
+     pa_assert(pa_thread_mq_get() || !PA_SOURCE_IS_LINKED((s)->state))
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0063-sink-input-source-output-Assign-to-volume-from-only-.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0063-sink-input-source-output-Assign-to-volume-from-only-.patch
new file mode 100644 (file)
index 0000000..38b3293
--- /dev/null
@@ -0,0 +1,506 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 7 Apr 2014 12:48:15 +0300
+Subject: sink-input, source-output: Assign to volume from only one place
+
+Forcing all volume changes to go through set_volume_direct() makes
+it easier to check where the stream volume is changed, and it also
+allows us to have only one place where notifications for changed
+volume are sent.
+
+Change-Id: Ie61bcc5747b419bb83c19a3ed78fd9f4d8a73cce
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/sink-input.c    | 69 +++++++++++++++++++++----------------------
+ src/pulsecore/sink-input.h    |  7 +++++
+ src/pulsecore/sink.c          | 53 +++++++++------------------------
+ src/pulsecore/source-output.c | 62 ++++++++++++++++++++------------------
+ src/pulsecore/source-output.h |  7 +++++
+ src/pulsecore/source.c        | 54 +++++++++------------------------
+ 6 files changed, 108 insertions(+), 144 deletions(-)
+
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index 6596eeb..83a0493 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -1292,7 +1292,7 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool s
+         return;
+     }
+-    i->volume = *volume;
++    pa_sink_input_set_volume_direct(i, volume);
+     i->save_volume = save;
+     if (pa_sink_flat_volume_enabled(i->sink)) {
+@@ -1310,13 +1310,6 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool s
+         /* Copy the new soft_volume to the thread_info struct */
+         pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0);
+     }
+-
+-    /* The volume changed, let's tell people so */
+-    if (i->volume_changed)
+-        i->volume_changed(i);
+-
+-    /* The virtual volume changed, let's tell people so */
+-    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ }
+ void pa_sink_input_add_volume_factor(pa_sink_input *i, const char *key, const pa_cvolume *volume_factor) {
+@@ -1715,7 +1708,6 @@ int pa_sink_input_start_move(pa_sink_input *i) {
+  * then also the origin sink and all streams connected to it need to update
+  * their volume - this function does all that by using recursion. */
+ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
+-    pa_cvolume old_volume;
+     pa_cvolume new_volume;
+     pa_assert(i);
+@@ -1765,19 +1757,11 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
+              *          always have volume_factor as soft_volume, so no change
+              *          should be needed) */
+-            old_volume = i->volume;
+-            pa_cvolume_reset(&i->volume, i->volume.channels);
++            pa_cvolume_reset(&new_volume, i->volume.channels);
++            pa_sink_input_set_volume_direct(i, &new_volume);
+             pa_cvolume_reset(&i->reference_ratio, i->reference_ratio.channels);
+             pa_assert(pa_cvolume_is_norm(&i->real_ratio));
+             pa_assert(pa_cvolume_equal(&i->soft_volume, &i->volume_factor));
+-
+-            /* Notify others about the changed sink input volume. */
+-            if (!pa_cvolume_equal(&i->volume, &old_volume)) {
+-                if (i->volume_changed)
+-                    i->volume_changed(i);
+-
+-                pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+-            }
+         }
+         /* Additionally, the origin sink volume needs updating:
+@@ -1809,8 +1793,6 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
+             update_volume_due_to_moving(origin_sink_input, dest);
+     } else {
+-        old_volume = i->volume;
+-
+         if (pa_sink_flat_volume_enabled(i->sink)) {
+             /* Ok, so this is a regular stream, and flat volume is enabled. The
+              * volume will have to be updated as follows:
+@@ -1822,9 +1804,10 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
+              *     i->soft_volume := i->real_ratio * i->volume_factor
+              *         (handled later by pa_sink_set_volume) */
+-            i->volume = i->sink->reference_volume;
+-            pa_cvolume_remap(&i->volume, &i->sink->channel_map, &i->channel_map);
+-            pa_sw_cvolume_multiply(&i->volume, &i->volume, &i->reference_ratio);
++            new_volume = i->sink->reference_volume;
++            pa_cvolume_remap(&new_volume, &i->sink->channel_map, &i->channel_map);
++            pa_sw_cvolume_multiply(&new_volume, &new_volume, &i->reference_ratio);
++            pa_sink_input_set_volume_direct(i, &new_volume);
+         } else {
+             /* Ok, so this is a regular stream, and flat volume is disabled.
+@@ -1835,21 +1818,10 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
+              *     i->real_ratio := i->reference_ratio
+              *     i->soft_volume := i->real_ratio * i->volume_factor */
+-            i->volume = i->reference_ratio;
++            pa_sink_input_set_volume_direct(i, &i->reference_ratio);
+             i->real_ratio = i->reference_ratio;
+             pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor);
+         }
+-
+-        /* Notify others about the changed sink input volume. */
+-        if (!pa_cvolume_equal(&i->volume, &old_volume)) {
+-            /* XXX: In case i->sink has flat volume enabled, then real_ratio
+-             * and soft_volume are not updated yet. Let's hope that the
+-             * callback implementation doesn't care about those variables... */
+-            if (i->volume_changed)
+-                i->volume_changed(i);
+-
+-            pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+-        }
+     }
+     /* If i->sink == dest, then recursion has finished, and we can finally call
+@@ -2333,3 +2305,28 @@ int pa_sink_input_update_rate(pa_sink_input *i) {
+     return 0;
+ }
++
++/* Called from the main thread. */
++void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume) {
++    pa_cvolume old_volume;
++    char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++    char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++
++    pa_assert(i);
++    pa_assert(volume);
++
++    old_volume = i->volume;
++
++    if (pa_cvolume_equal(volume, &old_volume))
++        return;
++
++    i->volume = *volume;
++    pa_log_debug("The volume of sink input %u changed from %s to %s.", i->index,
++                 pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &i->channel_map, true),
++                 pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &i->channel_map, true));
++
++    if (i->volume_changed)
++        i->volume_changed(i);
++
++    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
++}
+diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
+index deea348..4e7b229 100644
+--- a/src/pulsecore/sink-input.h
++++ b/src/pulsecore/sink-input.h
+@@ -431,6 +431,13 @@ bool pa_sink_input_process_underrun(pa_sink_input *i);
+ pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret);
++/* Called from the main thread, from sink.c only. The normal way to set the
++ * sink input volume is to call pa_sink_input_set_volume(), but the flat volume
++ * logic in sink.c needs also a function that doesn't do all the extra stuff
++ * that pa_sink_input_set_volume() does. This function simply sets i->volume
++ * and fires change notifications. */
++void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume);
++
+ #define pa_sink_input_assert_io_context(s) \
+     pa_assert(pa_thread_mq_get() || !PA_SINK_INPUT_IS_LINKED((s)->state))
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index e00dce5..94046b1 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -1898,20 +1898,13 @@ static void update_real_volume(pa_sink *s, const pa_cvolume *new_volume, pa_chan
+     PA_IDXSET_FOREACH(i, s->inputs, idx) {
+         if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+             if (pa_sink_flat_volume_enabled(s)) {
+-                pa_cvolume old_volume = i->volume;
++                pa_cvolume new_input_volume;
+                 /* Follow the root sink's real volume. */
+-                i->volume = *new_volume;
+-                pa_cvolume_remap(&i->volume, channel_map, &i->channel_map);
++                new_input_volume = *new_volume;
++                pa_cvolume_remap(&new_input_volume, channel_map, &i->channel_map);
++                pa_sink_input_set_volume_direct(i, &new_input_volume);
+                 compute_reference_ratio(i);
+-
+-                /* The volume changed, let's tell people so */
+-                if (!pa_cvolume_equal(&old_volume, &i->volume)) {
+-                    if (i->volume_changed)
+-                        i->volume_changed(i);
+-
+-                    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+-                }
+             }
+             update_real_volume(i->origin_sink, new_volume, channel_map);
+@@ -1966,7 +1959,7 @@ static void propagate_reference_volume(pa_sink *s) {
+      * sink input volumes accordingly */
+     PA_IDXSET_FOREACH(i, s->inputs, idx) {
+-        pa_cvolume old_volume;
++        pa_cvolume new_volume;
+         if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+             propagate_reference_volume(i->origin_sink);
+@@ -1977,24 +1970,14 @@ static void propagate_reference_volume(pa_sink *s) {
+             continue;
+         }
+-        old_volume = i->volume;
+-
+         /* This basically calculates:
+          *
+          * i->volume := s->reference_volume * i->reference_ratio  */
+-        i->volume = s->reference_volume;
+-        pa_cvolume_remap(&i->volume, &s->channel_map, &i->channel_map);
+-        pa_sw_cvolume_multiply(&i->volume, &i->volume, &i->reference_ratio);
+-
+-        /* The volume changed, let's tell people so */
+-        if (!pa_cvolume_equal(&old_volume, &i->volume)) {
+-
+-            if (i->volume_changed)
+-                i->volume_changed(i);
+-
+-            pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+-        }
++        new_volume = s->reference_volume;
++        pa_cvolume_remap(&new_volume, &s->channel_map, &i->channel_map);
++        pa_sw_cvolume_multiply(&new_volume, &new_volume, &i->reference_ratio);
++        pa_sink_input_set_volume_direct(i, &new_volume);
+     }
+ }
+@@ -2215,7 +2198,7 @@ static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume)
+     if (pa_sink_flat_volume_enabled(s)) {
+         PA_IDXSET_FOREACH(i, s->inputs, idx) {
+-            pa_cvolume old_volume = i->volume;
++            pa_cvolume new_volume;
+             /* 2. Since the sink's reference and real volumes are equal
+              * now our ratios should be too. */
+@@ -2229,18 +2212,10 @@ static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume)
+              * i->volume = s->reference_volume * i->reference_ratio
+              *
+              * This is identical to propagate_reference_volume() */
+-            i->volume = s->reference_volume;
+-            pa_cvolume_remap(&i->volume, &s->channel_map, &i->channel_map);
+-            pa_sw_cvolume_multiply(&i->volume, &i->volume, &i->reference_ratio);
+-
+-            /* Notify if something changed */
+-            if (!pa_cvolume_equal(&old_volume, &i->volume)) {
+-
+-                if (i->volume_changed)
+-                    i->volume_changed(i);
+-
+-                pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+-            }
++            new_volume = s->reference_volume;
++            pa_cvolume_remap(&new_volume, &s->channel_map, &i->channel_map);
++            pa_sw_cvolume_multiply(&new_volume, &new_volume, &i->reference_ratio);
++            pa_sink_input_set_volume_direct(i, &new_volume);
+             if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+                 propagate_real_volume(i->origin_sink, old_real_volume);
+diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
+index 169d98d..761323e 100644
+--- a/src/pulsecore/source-output.c
++++ b/src/pulsecore/source-output.c
+@@ -972,7 +972,7 @@ void pa_source_output_set_volume(pa_source_output *o, const pa_cvolume *volume,
+         return;
+     }
+-    o->volume = *volume;
++    pa_source_output_set_volume_direct(o, volume);
+     o->save_volume = save;
+     if (pa_source_flat_volume_enabled(o->source)) {
+@@ -1262,7 +1262,6 @@ int pa_source_output_start_move(pa_source_output *o) {
+  * then also the origin source and all streams connected to it need to update
+  * their volume - this function does all that by using recursion. */
+ static void update_volume_due_to_moving(pa_source_output *o, pa_source *dest) {
+-    pa_cvolume old_volume;
+     pa_cvolume new_volume;
+     pa_assert(o);
+@@ -1314,19 +1313,11 @@ static void update_volume_due_to_moving(pa_source_output *o, pa_source *dest) {
+              *          always have volume_factor as soft_volume, so no change
+              *          should be needed) */
+-            old_volume = o->volume;
+-            pa_cvolume_reset(&o->volume, o->volume.channels);
++            pa_cvolume_reset(&new_volume, o->volume.channels);
++            pa_source_output_set_volume_direct(o, &new_volume);
+             pa_cvolume_reset(&o->reference_ratio, o->reference_ratio.channels);
+             pa_assert(pa_cvolume_is_norm(&o->real_ratio));
+             pa_assert(pa_cvolume_equal(&o->soft_volume, &o->volume_factor));
+-
+-            /* Notify others about the changed source output volume. */
+-            if (!pa_cvolume_equal(&o->volume, &old_volume)) {
+-                if (o->volume_changed)
+-                    o->volume_changed(o);
+-
+-                pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+-            }
+         }
+         /* Additionally, the origin source volume needs updating:
+@@ -1358,8 +1349,6 @@ static void update_volume_due_to_moving(pa_source_output *o, pa_source *dest) {
+             update_volume_due_to_moving(destination_source_output, dest);
+     } else {
+-        old_volume = o->volume;
+-
+         if (pa_source_flat_volume_enabled(o->source)) {
+             /* Ok, so this is a regular stream, and flat volume is enabled. The
+              * volume will have to be updated as follows:
+@@ -1371,9 +1360,10 @@ static void update_volume_due_to_moving(pa_source_output *o, pa_source *dest) {
+              *     o->soft_volume := o->real_ratio * o->volume_factor
+              *         (handled later by pa_source_set_volume) */
+-            o->volume = o->source->reference_volume;
+-            pa_cvolume_remap(&o->volume, &o->source->channel_map, &o->channel_map);
+-            pa_sw_cvolume_multiply(&o->volume, &o->volume, &o->reference_ratio);
++            new_volume = o->source->reference_volume;
++            pa_cvolume_remap(&new_volume, &o->source->channel_map, &o->channel_map);
++            pa_sw_cvolume_multiply(&new_volume, &new_volume, &o->reference_ratio);
++            pa_source_output_set_volume_direct(o, &new_volume);
+         } else {
+             /* Ok, so this is a regular stream, and flat volume is disabled.
+@@ -1384,21 +1374,10 @@ static void update_volume_due_to_moving(pa_source_output *o, pa_source *dest) {
+              *     o->real_ratio := o->reference_ratio
+              *     o->soft_volume := o->real_ratio * o->volume_factor */
+-            o->volume = o->reference_ratio;
++            pa_source_output_set_volume_direct(o, &o->reference_ratio);
+             o->real_ratio = o->reference_ratio;
+             pa_sw_cvolume_multiply(&o->soft_volume, &o->real_ratio, &o->volume_factor);
+         }
+-
+-        /* Notify others about the changed source output volume. */
+-        if (!pa_cvolume_equal(&o->volume, &old_volume)) {
+-            /* XXX: In case o->source has flat volume enabled, then real_ratio
+-             * and soft_volume are not updated yet. Let's hope that the
+-             * callback implementation doesn't care about those variables... */
+-            if (o->volume_changed)
+-                o->volume_changed(o);
+-
+-            pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+-        }
+     }
+     /* If o->source == dest, then recursion has finished, and we can finally call
+@@ -1692,3 +1671,28 @@ int pa_source_output_update_rate(pa_source_output *o) {
+     return 0;
+ }
++
++/* Called from the main thread. */
++void pa_source_output_set_volume_direct(pa_source_output *o, const pa_cvolume *volume) {
++    pa_cvolume old_volume;
++    char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++    char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++
++    pa_assert(o);
++    pa_assert(volume);
++
++    old_volume = o->volume;
++
++    if (pa_cvolume_equal(volume, &old_volume))
++        return;
++
++    o->volume = *volume;
++    pa_log_debug("The volume of source output %u changed from %s to %s.", o->index,
++                 pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &o->channel_map, true),
++                 pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &o->channel_map, true));
++
++    if (o->volume_changed)
++        o->volume_changed(o);
++
++    pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
++}
+diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
+index 102fb8b..27d6fd4 100644
+--- a/src/pulsecore/source-output.h
++++ b/src/pulsecore/source-output.h
+@@ -353,6 +353,13 @@ int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int
+ pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec);
++/* Called from the main thread, from source.c only. The normal way to set the
++ * source output volume is to call pa_source_output_set_volume(), but the flat
++ * volume logic in source.c needs also a function that doesn't do all the extra
++ * stuff that pa_source_output_set_volume() does. This function simply sets
++ * o->volume and fires change notifications. */
++void pa_source_output_set_volume_direct(pa_source_output *o, const pa_cvolume *volume);
++
+ #define pa_source_output_assert_io_context(s) \
+     pa_assert(pa_thread_mq_get() || !PA_SOURCE_OUTPUT_IS_LINKED((s)->state))
+diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
+index f58bb44..c8165e6 100644
+--- a/src/pulsecore/source.c
++++ b/src/pulsecore/source.c
+@@ -1443,20 +1443,13 @@ static void update_real_volume(pa_source *s, const pa_cvolume *new_volume, pa_ch
+     PA_IDXSET_FOREACH(o, s->outputs, idx) {
+         if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+             if (pa_source_flat_volume_enabled(s)) {
+-                pa_cvolume old_volume = o->volume;
++                pa_cvolume new_output_volume;
+                 /* Follow the root source's real volume. */
+-                o->volume = *new_volume;
+-                pa_cvolume_remap(&o->volume, channel_map, &o->channel_map);
++                new_output_volume = *new_volume;
++                pa_cvolume_remap(&new_output_volume, channel_map, &o->channel_map);
++                pa_source_output_set_volume_direct(o, &new_output_volume);
+                 compute_reference_ratio(o);
+-
+-                /* The volume changed, let's tell people so */
+-                if (!pa_cvolume_equal(&old_volume, &o->volume)) {
+-                    if (o->volume_changed)
+-                        o->volume_changed(o);
+-
+-                    pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+-                }
+             }
+             update_real_volume(o->destination_source, new_volume, channel_map);
+@@ -1511,7 +1504,7 @@ static void propagate_reference_volume(pa_source *s) {
+      * source output volumes accordingly */
+     PA_IDXSET_FOREACH(o, s->outputs, idx) {
+-        pa_cvolume old_volume;
++        pa_cvolume new_volume;
+         if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+             propagate_reference_volume(o->destination_source);
+@@ -1522,24 +1515,14 @@ static void propagate_reference_volume(pa_source *s) {
+             continue;
+         }
+-        old_volume = o->volume;
+-
+         /* This basically calculates:
+          *
+          * o->volume := o->reference_volume * o->reference_ratio  */
+-        o->volume = s->reference_volume;
+-        pa_cvolume_remap(&o->volume, &s->channel_map, &o->channel_map);
+-        pa_sw_cvolume_multiply(&o->volume, &o->volume, &o->reference_ratio);
+-
+-        /* The volume changed, let's tell people so */
+-        if (!pa_cvolume_equal(&old_volume, &o->volume)) {
+-
+-            if (o->volume_changed)
+-                o->volume_changed(o);
+-
+-            pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+-        }
++        new_volume = s->reference_volume;
++        pa_cvolume_remap(&new_volume, &s->channel_map, &o->channel_map);
++        pa_sw_cvolume_multiply(&new_volume, &new_volume, &o->reference_ratio);
++        pa_source_output_set_volume_direct(o, &new_volume);
+     }
+ }
+@@ -1732,9 +1715,8 @@ static void propagate_real_volume(pa_source *s, const pa_cvolume *old_real_volum
+     }
+     if (pa_source_flat_volume_enabled(s)) {
+-
+         PA_IDXSET_FOREACH(o, s->outputs, idx) {
+-            pa_cvolume old_volume = o->volume;
++            pa_cvolume new_volume;
+             /* 2. Since the source's reference and real volumes are equal
+              * now our ratios should be too. */
+@@ -1748,18 +1730,10 @@ static void propagate_real_volume(pa_source *s, const pa_cvolume *old_real_volum
+              * o->volume = s->reference_volume * o->reference_ratio
+              *
+              * This is identical to propagate_reference_volume() */
+-            o->volume = s->reference_volume;
+-            pa_cvolume_remap(&o->volume, &s->channel_map, &o->channel_map);
+-            pa_sw_cvolume_multiply(&o->volume, &o->volume, &o->reference_ratio);
+-
+-            /* Notify if something changed */
+-            if (!pa_cvolume_equal(&old_volume, &o->volume)) {
+-
+-                if (o->volume_changed)
+-                    o->volume_changed(o);
+-
+-                pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+-            }
++            new_volume = s->reference_volume;
++            pa_cvolume_remap(&new_volume, &s->channel_map, &o->channel_map);
++            pa_sw_cvolume_multiply(&new_volume, &new_volume, &o->reference_ratio);
++            pa_source_output_set_volume_direct(o, &new_volume);
+             if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+                 propagate_real_volume(o->destination_source, old_real_volume);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0064-sink-source-Return-early-from-set_mute.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0064-sink-source-Return-early-from-set_mute.patch
new file mode 100644 (file)
index 0000000..4cd0fdb
--- /dev/null
@@ -0,0 +1,75 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 14 Apr 2014 14:03:24 +0300
+Subject: sink, source: Return early from set_mute()
+
+This avoids redundant set_mute() callback calls.
+
+Some logging was added too.
+
+Change-Id: I10188c3b43d61fe751abe0f9940015af35c4a137
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/sink.c   | 13 +++++++++----
+ src/pulsecore/source.c | 13 +++++++++----
+ 2 files changed, 18 insertions(+), 8 deletions(-)
+
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index 94046b1..3eed550 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -2288,16 +2288,21 @@ void pa_sink_set_mute(pa_sink *s, bool mute, bool save) {
+     pa_assert(PA_SINK_IS_LINKED(s->state));
+     old_muted = s->muted;
++
++    if (mute == old_muted) {
++        s->save_muted |= save;
++        return;
++    }
++
+     s->muted = mute;
+-    s->save_muted = (old_muted == s->muted && s->save_muted) || save;
++    s->save_muted = save;
+     if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->set_mute)
+         s->set_mute(s);
++    pa_log_debug("The mute of sink %s changed from %s to %s.", s->name, pa_yes_no(old_muted), pa_yes_no(mute));
+     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
+-
+-    if (old_muted != s->muted)
+-        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
++    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ }
+ /* Called from main thread */
+diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
+index c8165e6..4f4aea3 100644
+--- a/src/pulsecore/source.c
++++ b/src/pulsecore/source.c
+@@ -1806,16 +1806,21 @@ void pa_source_set_mute(pa_source *s, bool mute, bool save) {
+     pa_assert(PA_SOURCE_IS_LINKED(s->state));
+     old_muted = s->muted;
++
++    if (mute == old_muted) {
++        s->save_muted |= save;
++        return;
++    }
++
+     s->muted = mute;
+-    s->save_muted = (old_muted == s->muted && s->save_muted) || save;
++    s->save_muted = save;
+     if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->set_mute)
+         s->set_mute(s);
++    pa_log_debug("The mute of source %s changed from %s to %s.", s->name, pa_yes_no(old_muted), pa_yes_no(mute));
+     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
+-
+-    if (old_muted != s->muted)
+-        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
++    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ }
+ /* Called from main thread */
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0065-sink-input-source-output-Add-logging-to-set_mute.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0065-sink-input-source-output-Add-logging-to-set_mute.patch
new file mode 100644 (file)
index 0000000..a60d1d2
--- /dev/null
@@ -0,0 +1,67 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 14 Apr 2014 14:13:56 +0300
+Subject: sink-input, source-output: Add logging to set_mute()
+
+Change-Id: Ie10aa76cae75c7b6a52ea4a9039b8e3e37a748b2
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/sink-input.c    | 8 +++++++-
+ src/pulsecore/source-output.c | 8 +++++++-
+ 2 files changed, 14 insertions(+), 2 deletions(-)
+
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index 83a0493..f706acc 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -1458,16 +1458,22 @@ pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, bool
+ /* Called from main context */
+ void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save) {
++    bool old_mute;
++
+     pa_sink_input_assert_ref(i);
+     pa_assert_ctl_context();
+     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+-    if (!i->muted == !mute) {
++    old_mute = i->muted;
++
++    if (mute == old_mute) {
+         i->save_muted |= save;
+         return;
+     }
+     i->muted = mute;
++    pa_log_debug("The mute of sink input %u changed from %s to %s.", i->index, pa_yes_no(old_mute), pa_yes_no(mute));
++
+     i->save_muted = save;
+     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0);
+diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
+index 761323e..bb89384 100644
+--- a/src/pulsecore/source-output.c
++++ b/src/pulsecore/source-output.c
+@@ -1057,16 +1057,22 @@ pa_cvolume *pa_source_output_get_volume(pa_source_output *o, pa_cvolume *volume,
+ /* Called from main context */
+ void pa_source_output_set_mute(pa_source_output *o, bool mute, bool save) {
++    bool old_mute;
++
+     pa_source_output_assert_ref(o);
+     pa_assert_ctl_context();
+     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+-    if (!o->muted == !mute) {
++    old_mute = o->muted;
++
++    if (mute == old_mute) {
+         o->save_muted |= save;
+         return;
+     }
+     o->muted = mute;
++    pa_log_debug("The mute of source output %u changed from %s to %s.", o->index, pa_yes_no(old_mute), pa_yes_no(mute));
++
+     o->save_muted = save;
+     pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0066-sink-source-Allow-calling-set_mute-during-initializa.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0066-sink-source-Allow-calling-set_mute-during-initializa.patch
new file mode 100644 (file)
index 0000000..02ac7b5
--- /dev/null
@@ -0,0 +1,61 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 14 Apr 2014 14:43:23 +0300
+Subject: sink, source: Allow calling set_mute() during initialization
+
+Currently the alsa sink and source write directly to s->muted during
+initialization, but I think it's better to avoid direct writes, and
+use the set_mute() function instead, because that makes it easier to
+figure out where s->muted is modified. This patch prevents the
+set_mute() call from crashing in the state assertion.
+
+Change-Id: I12220fb2668723931bebbe1484f115016f1edf25
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/sink.c   | 4 +++-
+ src/pulsecore/source.c | 4 +++-
+ 2 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index 3eed550..7eb4cb4 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -2285,7 +2285,6 @@ void pa_sink_set_mute(pa_sink *s, bool mute, bool save) {
+     pa_sink_assert_ref(s);
+     pa_assert_ctl_context();
+-    pa_assert(PA_SINK_IS_LINKED(s->state));
+     old_muted = s->muted;
+@@ -2300,6 +2299,9 @@ void pa_sink_set_mute(pa_sink *s, bool mute, bool save) {
+     if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->set_mute)
+         s->set_mute(s);
++    if (!PA_SINK_IS_LINKED(s->state))
++        return;
++
+     pa_log_debug("The mute of sink %s changed from %s to %s.", s->name, pa_yes_no(old_muted), pa_yes_no(mute));
+     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
+     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
+index 4f4aea3..3b6ad44 100644
+--- a/src/pulsecore/source.c
++++ b/src/pulsecore/source.c
+@@ -1803,7 +1803,6 @@ void pa_source_set_mute(pa_source *s, bool mute, bool save) {
+     pa_source_assert_ref(s);
+     pa_assert_ctl_context();
+-    pa_assert(PA_SOURCE_IS_LINKED(s->state));
+     old_muted = s->muted;
+@@ -1818,6 +1817,9 @@ void pa_source_set_mute(pa_source *s, bool mute, bool save) {
+     if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->set_mute)
+         s->set_mute(s);
++    if (!PA_SOURCE_IS_LINKED(s->state))
++        return;
++
+     pa_log_debug("The mute of source %s changed from %s to %s.", s->name, pa_yes_no(old_muted), pa_yes_no(mute));
+     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
+     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0067-echo-cancel-Remove-redundant-get_mute-callback.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0067-echo-cancel-Remove-redundant-get_mute-callback.patch
new file mode 100644 (file)
index 0000000..4962b8d
--- /dev/null
@@ -0,0 +1,47 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 14 Apr 2014 15:13:08 +0300
+Subject: echo-cancel: Remove redundant get_mute() callback
+
+The callback just called pa_source_output_get_mute(), which doesn't
+have any side effects, and the return value wasn't used either, so
+the callback was essentially a no-op.
+
+Change-Id: Ic16824b06393f59b55087842da64c7d09035b9e8
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/echo-cancel/module-echo-cancel.c | 15 ---------------
+ 1 file changed, 15 deletions(-)
+
+diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
+index 29eed13..8f791ce 100644
+--- a/src/modules/echo-cancel/module-echo-cancel.c
++++ b/src/modules/echo-cancel/module-echo-cancel.c
+@@ -656,20 +656,6 @@ static void sink_set_mute_cb(pa_sink *s) {
+     pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
+ }
+-/* Called from main context */
+-static void source_get_mute_cb(pa_source *s) {
+-    struct userdata *u;
+-
+-    pa_source_assert_ref(s);
+-    pa_assert_se(u = s->userdata);
+-
+-    if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
+-        !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
+-        return;
+-
+-    pa_source_output_get_mute(u->source_output);
+-}
+-
+ /* Called from source I/O thread context. */
+ static void apply_diff_time(struct userdata *u, int64_t diff_time) {
+     int64_t diff;
+@@ -1857,7 +1843,6 @@ int pa__init(pa_module*m) {
+     u->source->parent.process_msg = source_process_msg_cb;
+     u->source->set_state = source_set_state_cb;
+     u->source->update_requested_latency = source_update_requested_latency_cb;
+-    pa_source_set_get_mute_callback(u->source, source_get_mute_cb);
+     pa_source_set_set_mute_callback(u->source, source_set_mute_cb);
+     if (!u->use_volume_sharing) {
+         pa_source_set_get_volume_callback(u->source, source_get_volume_cb);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0068-sink-source-Call-set_mute-from-mute_changed.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0068-sink-source-Call-set_mute-from-mute_changed.patch
new file mode 100644 (file)
index 0000000..42422ea
--- /dev/null
@@ -0,0 +1,133 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Tue, 15 Apr 2014 09:39:49 +0300
+Subject: sink, source: Call set_mute() from mute_changed()
+
+This refactoring reduces duplication, as mute_changed() used to do the
+same things as set_mute(). Other benefits are improved logging
+(set_mute() logs the mute change, mute_changed() used to not do that)
+and the soft mute state is kept up to date, because set_mute() sends
+the SET_MUTE message to the IO thread.
+
+The set_mute_in_progress flag is an extra precaution for preventing
+recursion in case a sink/source implementation's set_mute() callback
+causes mute_changed() to be called. Currently there are no such
+implementations, but I think that would be a valid thing to do, so
+some day there might be such implementation.
+
+Change-Id: I33c81f4034001f777c4533c2c63eada67548c683
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/sink.c   | 19 ++++++++++++-------
+ src/pulsecore/sink.h   |  2 ++
+ src/pulsecore/source.c | 19 ++++++++++++-------
+ src/pulsecore/source.h |  2 ++
+ 4 files changed, 28 insertions(+), 14 deletions(-)
+
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index 7eb4cb4..4c348b5 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -2296,8 +2296,11 @@ void pa_sink_set_mute(pa_sink *s, bool mute, bool save) {
+     s->muted = mute;
+     s->save_muted = save;
+-    if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->set_mute)
++    if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->set_mute) {
++        s->set_mute_in_progress = true;
+         s->set_mute(s);
++        s->set_mute_in_progress = false;
++    }
+     if (!PA_SINK_IS_LINKED(s->state))
+         return;
+@@ -2341,15 +2344,17 @@ void pa_sink_mute_changed(pa_sink *s, bool new_muted) {
+     pa_assert_ctl_context();
+     pa_assert(PA_SINK_IS_LINKED(s->state));
+-    /* The sink implementor may call this if the volume changed to make sure everyone is notified */
+-
+-    if (s->muted == new_muted)
++    if (s->set_mute_in_progress)
+         return;
+-    s->muted = new_muted;
+-    s->save_muted = true;
++    /* pa_sink_set_mute() does this same check, so this may appear redundant,
++     * but we must have this here also, because the save parameter of
++     * pa_sink_set_mute() would otherwise have unintended side effects (saving
++     * the mute state when it shouldn't be saved). */
++    if (new_muted == s->muted)
++        return;
+-    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
++    pa_sink_set_mute(s, new_muted, true);
+ }
+ /* Called from main thread */
+diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
+index 41a439b..72437a4 100644
+--- a/src/pulsecore/sink.h
++++ b/src/pulsecore/sink.h
+@@ -122,6 +122,8 @@ struct pa_sink {
+     unsigned priority;
++    bool set_mute_in_progress;
++
+     /* Called when the main loop requests a state change. Called from
+      * main loop context. If returns -1 the state change will be
+      * inhibited */
+diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
+index 3b6ad44..2f6aaad 100644
+--- a/src/pulsecore/source.c
++++ b/src/pulsecore/source.c
+@@ -1814,8 +1814,11 @@ void pa_source_set_mute(pa_source *s, bool mute, bool save) {
+     s->muted = mute;
+     s->save_muted = save;
+-    if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->set_mute)
++    if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->set_mute) {
++        s->set_mute_in_progress = true;
+         s->set_mute(s);
++        s->set_mute_in_progress = false;
++    }
+     if (!PA_SOURCE_IS_LINKED(s->state))
+         return;
+@@ -1859,15 +1862,17 @@ void pa_source_mute_changed(pa_source *s, bool new_muted) {
+     pa_assert_ctl_context();
+     pa_assert(PA_SOURCE_IS_LINKED(s->state));
+-    /* The source implementor may call this if the mute state changed to make sure everyone is notified */
+-
+-    if (s->muted == new_muted)
++    if (s->set_mute_in_progress)
+         return;
+-    s->muted = new_muted;
+-    s->save_muted = true;
++    /* pa_source_set_mute() does this same check, so this may appear redundant,
++     * but we must have this here also, because the save parameter of
++     * pa_source_set_mute() would otherwise have unintended side effects
++     * (saving the mute state when it shouldn't be saved). */
++    if (new_muted == s->muted)
++        return;
+-    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
++    pa_source_set_mute(s, new_muted, true);
+ }
+ /* Called from main thread */
+diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
+index 6318595..ca2ed59 100644
+--- a/src/pulsecore/source.h
++++ b/src/pulsecore/source.h
+@@ -118,6 +118,8 @@ struct pa_source {
+     unsigned priority;
++    bool set_mute_in_progress;
++
+     /* Called when the main loop requests a state change. Called from
+      * main loop context. If returns -1 the state change will be
+      * inhibited */
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0069-sink-source-Assign-to-s-muted-from-only-one-place.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0069-sink-source-Assign-to-s-muted-from-only-one-place.patch
new file mode 100644 (file)
index 0000000..c7332fd
--- /dev/null
@@ -0,0 +1,346 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 14 Apr 2014 14:52:16 +0300
+Subject: sink, source: Assign to s->muted from only one place
+
+Forcing all mute changes to go through set_mute() makes it easier to
+check where the muted field is changed, and it also allows us to have
+only one place where notifications for changed mute are sent.
+
+Change-Id: Idb1bd6ef923a165e249d42265ebedc30a6c8fca4
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/alsa/alsa-sink.c   | 17 ++++++++++-------
+ src/modules/alsa/alsa-source.c | 17 ++++++++++-------
+ src/modules/module-solaris.c   | 17 +++++++++++------
+ src/pulsecore/sink.c           | 26 ++++++++++----------------
+ src/pulsecore/sink.h           | 24 ++++++++++++++++++------
+ src/pulsecore/source.c         | 26 ++++++++++----------------
+ src/pulsecore/source.h         | 24 ++++++++++++++++++------
+ 7 files changed, 87 insertions(+), 64 deletions(-)
+
+diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
+index 03babb3..2bc27ff 100644
+--- a/src/modules/alsa/alsa-sink.c
++++ b/src/modules/alsa/alsa-sink.c
+@@ -1401,18 +1401,17 @@ static void sink_write_volume_cb(pa_sink *s) {
+     }
+ }
+-static void sink_get_mute_cb(pa_sink *s) {
++static int sink_get_mute_cb(pa_sink *s, bool *mute) {
+     struct userdata *u = s->userdata;
+-    bool b;
+     pa_assert(u);
+     pa_assert(u->mixer_path);
+     pa_assert(u->mixer_handle);
+-    if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, &b) < 0)
+-        return;
++    if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, mute) < 0)
++        return -1;
+-    s->muted = b;
++    return 0;
+ }
+ static void sink_set_mute_cb(pa_sink *s) {
+@@ -2399,8 +2398,12 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
+         if (u->sink->set_mute)
+             u->sink->set_mute(u->sink);
+     } else {
+-        if (u->sink->get_mute)
+-            u->sink->get_mute(u->sink);
++        if (u->sink->get_mute) {
++            bool mute;
++
++            if (u->sink->get_mute(u->sink, &mute) >= 0)
++                pa_sink_set_mute(u->sink, mute, false);
++        }
+     }
+     if ((data.volume_is_set || data.muted_is_set) && u->sink->write_volume)
+diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
+index 2e93e0f..e181a30 100644
+--- a/src/modules/alsa/alsa-source.c
++++ b/src/modules/alsa/alsa-source.c
+@@ -1277,18 +1277,17 @@ static void source_write_volume_cb(pa_source *s) {
+     }
+ }
+-static void source_get_mute_cb(pa_source *s) {
++static int source_get_mute_cb(pa_source *s, bool *mute) {
+     struct userdata *u = s->userdata;
+-    bool b;
+     pa_assert(u);
+     pa_assert(u->mixer_path);
+     pa_assert(u->mixer_handle);
+-    if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, &b) < 0)
+-        return;
++    if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, mute) < 0)
++        return -1;
+-    s->muted = b;
++    return 0;
+ }
+ static void source_set_mute_cb(pa_source *s) {
+@@ -2088,8 +2087,12 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
+         if (u->source->set_mute)
+             u->source->set_mute(u->source);
+     } else {
+-        if (u->source->get_mute)
+-            u->source->get_mute(u->source);
++        if (u->source->get_mute) {
++            bool mute;
++
++            if (u->source->get_mute(u->source, &mute) >= 0)
++                pa_source_set_mute(u->source, mute, false);
++        }
+     }
+     if ((data.volume_is_set || data.muted_is_set) && u->source->write_volume)
+diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c
+index b4fa734..71a98e9 100644
+--- a/src/modules/module-solaris.c
++++ b/src/modules/module-solaris.c
+@@ -571,18 +571,23 @@ static void sink_set_mute(pa_sink *s) {
+     }
+ }
+-static void sink_get_mute(pa_sink *s) {
++static int sink_get_mute(pa_sink *s, bool *mute) {
+     struct userdata *u = s->userdata;
+     audio_info_t info;
+     pa_assert(u);
+-    if (u->fd >= 0) {
+-        if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0)
+-            pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
+-        else
+-            s->muted = !!info.output_muted;
++    if (u->fd < 0)
++        return -1;
++
++    if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0) {
++        pa_log("AUDIO_GETINFO: %s", pa_cstrerror(errno));
++        return -1;
+     }
++
++    *mute = info.output_muted;
++
++    return 0;
+ }
+ static void process_rewind(struct userdata *u) {
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index 4c348b5..b64001b 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -519,7 +519,7 @@ void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
+         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ }
+-void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_cb_t cb) {
++void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_get_mute_cb_t cb) {
+     pa_assert(s);
+     s->get_mute = cb;
+@@ -2317,21 +2317,15 @@ bool pa_sink_get_mute(pa_sink *s, bool force_refresh) {
+     pa_assert_ctl_context();
+     pa_assert(PA_SINK_IS_LINKED(s->state));
+-    if (s->refresh_muted || force_refresh) {
+-        bool old_muted = s->muted;
++    if ((s->refresh_muted || force_refresh) && s->get_mute) {
++        bool mute;
+-        if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->get_mute)
+-            s->get_mute(s);
+-
+-        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0);
+-
+-        if (old_muted != s->muted) {
+-            s->save_muted = true;
+-
+-            pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+-
+-            /* Make sure the soft mute status stays in sync */
+-            pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
++        if (s->flags & PA_SINK_DEFERRED_VOLUME) {
++            if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, &mute, 0, NULL) >= 0)
++                pa_sink_mute_changed(s, mute);
++        } else {
++            if (s->get_mute(s, &mute) >= 0)
++                pa_sink_mute_changed(s, mute);
+         }
+     }
+@@ -2848,7 +2842,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
+         case PA_SINK_MESSAGE_GET_MUTE:
+             if (s->flags & PA_SINK_DEFERRED_VOLUME && s->get_mute)
+-                s->get_mute(s);
++                return s->get_mute(s, userdata);
+             return 0;
+diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
+index 72437a4..e069d02 100644
+--- a/src/pulsecore/sink.h
++++ b/src/pulsecore/sink.h
+@@ -59,6 +59,8 @@ static inline bool PA_SINK_IS_LINKED(pa_sink_state_t x) {
+ /* A generic definition for void callback functions */
+ typedef void(*pa_sink_cb_t)(pa_sink *s);
++typedef int (*pa_sink_get_mute_cb_t)(pa_sink *s, bool *mute);
++
+ struct pa_sink {
+     pa_msgobject parent;
+@@ -195,14 +197,24 @@ struct pa_sink {
+      * set this callback. */
+     pa_sink_cb_t write_volume; /* may be NULL */
+-    /* Called when the mute setting is queried. A PA_SINK_MESSAGE_GET_MUTE
+-     * message will also be sent. Called from IO thread if PA_SINK_DEFERRED_VOLUME
+-     * flag is set otherwise from main loop context. If refresh_mute is false
+-     * neither this function is called nor a message is sent.
++    /* If the sink mute can change "spontaneously" (i.e. initiated by the sink
++     * implementation, not by someone else calling pa_sink_set_mute()), then
++     * the sink implementation can notify about changed mute either by calling
++     * pa_sink_mute_changed() or by calling pa_sink_get_mute() with
++     * force_refresh=true. If the implementation chooses the latter approach,
++     * it should implement the get_mute callback. Otherwise get_mute can be
++     * NULL.
++     *
++     * This is called when pa_sink_get_mute() is called with
++     * force_refresh=true. This is called from the IO thread if the
++     * PA_SINK_DEFERRED_VOLUME flag is set, otherwise this is called from the
++     * main thread. On success, the implementation is expected to return 0 and
++     * set the mute parameter that is passed as a reference. On failure, the
++     * implementation is expected to return -1.
+      *
+      * You must use the function pa_sink_set_get_mute_callback() to
+      * set this callback. */
+-    pa_sink_cb_t get_mute; /* may be NULL */
++    pa_sink_get_mute_cb_t get_mute;
+     /* Called when the mute setting shall be changed. A PA_SINK_MESSAGE_SET_MUTE
+      * message will also be sent. Called from IO thread if PA_SINK_DEFERRED_VOLUME
+@@ -386,7 +398,7 @@ pa_sink* pa_sink_new(
+ void pa_sink_set_get_volume_callback(pa_sink *s, pa_sink_cb_t cb);
+ void pa_sink_set_set_volume_callback(pa_sink *s, pa_sink_cb_t cb);
+ void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb);
+-void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_cb_t cb);
++void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_get_mute_cb_t cb);
+ void pa_sink_set_set_mute_callback(pa_sink *s, pa_sink_cb_t cb);
+ void pa_sink_enable_decibel_volume(pa_sink *s, bool enable);
+diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
+index 2f6aaad..24e8be9 100644
+--- a/src/pulsecore/source.c
++++ b/src/pulsecore/source.c
+@@ -467,7 +467,7 @@ void pa_source_set_write_volume_callback(pa_source *s, pa_source_cb_t cb) {
+         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ }
+-void pa_source_set_get_mute_callback(pa_source *s, pa_source_cb_t cb) {
++void pa_source_set_get_mute_callback(pa_source *s, pa_source_get_mute_cb_t cb) {
+     pa_assert(s);
+     s->get_mute = cb;
+@@ -1835,21 +1835,15 @@ bool pa_source_get_mute(pa_source *s, bool force_refresh) {
+     pa_assert_ctl_context();
+     pa_assert(PA_SOURCE_IS_LINKED(s->state));
+-    if (s->refresh_muted || force_refresh) {
+-        bool old_muted = s->muted;
++    if ((s->refresh_muted || force_refresh) && s->get_mute) {
++        bool mute;
+-        if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->get_mute)
+-            s->get_mute(s);
+-
+-        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0);
+-
+-        if (old_muted != s->muted) {
+-            s->save_muted = true;
+-
+-            pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+-
+-            /* Make sure the soft mute status stays in sync */
+-            pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
++        if (s->flags & PA_SOURCE_DEFERRED_VOLUME) {
++            if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, &mute, 0, NULL) >= 0)
++                pa_source_mute_changed(s, mute);
++        } else {
++            if (s->get_mute(s, &mute) >= 0)
++                pa_source_mute_changed(s, mute);
+         }
+     }
+@@ -2136,7 +2130,7 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
+         case PA_SOURCE_MESSAGE_GET_MUTE:
+             if (s->flags & PA_SOURCE_DEFERRED_VOLUME && s->get_mute)
+-                s->get_mute(s);
++                return s->get_mute(s, userdata);
+             return 0;
+diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
+index ca2ed59..83bc2dd 100644
+--- a/src/pulsecore/source.h
++++ b/src/pulsecore/source.h
+@@ -58,6 +58,8 @@ static inline bool PA_SOURCE_IS_LINKED(pa_source_state_t x) {
+ /* A generic definition for void callback functions */
+ typedef void(*pa_source_cb_t)(pa_source *s);
++typedef int (*pa_source_get_mute_cb_t)(pa_source *s, bool *mute);
++
+ struct pa_source {
+     pa_msgobject parent;
+@@ -158,14 +160,24 @@ struct pa_source {
+      * set this callback. */
+     pa_source_cb_t write_volume; /* may be NULL */
+-    /* Called when the mute setting is queried. Called from main loop
+-     * context. If this is NULL a PA_SOURCE_MESSAGE_GET_MUTE message
+-     * will be sent to the IO thread instead. If refresh_mute is
+-     * false neither this function is called nor a message is sent.
++    /* If the source mute can change "spontaneously" (i.e. initiated by the
++     * source implementation, not by someone else calling
++     * pa_source_set_mute()), then the source implementation can notify about
++     * changed mute either by calling pa_source_mute_changed() or by calling
++     * pa_source_get_mute() with force_refresh=true. If the implementation
++     * chooses the latter approach, it should implement the get_mute callback.
++     * Otherwise get_mute can be NULL.
++     *
++     * This is called when pa_source_get_mute() is called with
++     * force_refresh=true. This is called from the IO thread if the
++     * PA_SOURCE_DEFERRED_VOLUME flag is set, otherwise this is called from the
++     * main thread. On success, the implementation is expected to return 0 and
++     * set the mute parameter that is passed as a reference. On failure, the
++     * implementation is expected to return -1.
+      *
+      * You must use the function pa_source_set_get_mute_callback() to
+      * set this callback. */
+-    pa_source_cb_t get_mute; /* may be NULL */
++    pa_source_get_mute_cb_t get_mute;
+     /* Called when the mute setting shall be changed. Called from main
+      * loop context. If this is NULL a PA_SOURCE_MESSAGE_SET_MUTE
+@@ -316,7 +328,7 @@ pa_source* pa_source_new(
+ void pa_source_set_get_volume_callback(pa_source *s, pa_source_cb_t cb);
+ void pa_source_set_set_volume_callback(pa_source *s, pa_source_cb_t cb);
+ void pa_source_set_write_volume_callback(pa_source *s, pa_source_cb_t cb);
+-void pa_source_set_get_mute_callback(pa_source *s, pa_source_cb_t cb);
++void pa_source_set_get_mute_callback(pa_source *s, pa_source_get_mute_cb_t cb);
+ void pa_source_set_set_mute_callback(pa_source *s, pa_source_cb_t cb);
+ void pa_source_enable_decibel_volume(pa_source *s, bool enable);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0070-sink-input-source-output-Remove-redundant-get_mute-f.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0070-sink-input-source-output-Remove-redundant-get_mute-f.patch
new file mode 100644 (file)
index 0000000..9b25a3d
--- /dev/null
@@ -0,0 +1,210 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 14 Apr 2014 15:24:31 +0300
+Subject: sink-input, source-output: Remove redundant get_mute() functions
+
+The functions just return the muted value. Callers can as well read
+the struct field directly, it's simpler that way.
+
+Change-Id: I368f7e09cdf522039a6573e5002f7544b4e840d3
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/dbus/iface-stream.c     | 4 ++--
+ src/modules/module-role-cork.c      | 9 ++++-----
+ src/modules/module-stream-restore.c | 4 ++--
+ src/pulsecore/cli-text.c            | 4 ++--
+ src/pulsecore/protocol-native.c     | 4 ++--
+ src/pulsecore/sink-input.c          | 9 ---------
+ src/pulsecore/sink-input.h          | 1 -
+ src/pulsecore/source-output.c       | 9 ---------
+ src/pulsecore/source-output.h       | 1 -
+ 9 files changed, 12 insertions(+), 33 deletions(-)
+
+diff --git a/src/modules/dbus/iface-stream.c b/src/modules/dbus/iface-stream.c
+index 1cff95e..4cbcd74 100644
+--- a/src/modules/dbus/iface-stream.c
++++ b/src/modules/dbus/iface-stream.c
+@@ -765,7 +765,7 @@ static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t
+             }
+         }
+-        new_mute = pa_sink_input_get_mute(s->sink_input);
++        new_mute = s->sink_input->muted;
+         if (s->mute != new_mute) {
+             s->mute = new_mute;
+@@ -861,7 +861,7 @@ pa_dbusiface_stream *pa_dbusiface_stream_new_playback(pa_dbusiface_core *core, p
+     else
+         pa_cvolume_init(&s->volume);
+-    s->mute = pa_sink_input_get_mute(sink_input);
++    s->mute = sink_input->muted;
+     s->proplist = pa_proplist_copy(sink_input->proplist);
+     s->dbus_protocol = pa_dbus_protocol_get(sink_input->core);
+     s->subscription = pa_subscription_new(sink_input->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, subscription_cb, s);
+diff --git a/src/modules/module-role-cork.c b/src/modules/module-role-cork.c
+index 6573cd6..8ca2109 100644
+--- a/src/modules/module-role-cork.c
++++ b/src/modules/module-role-cork.c
+@@ -102,7 +102,7 @@ static inline void apply_cork_to_sink(struct userdata *u, pa_sink *s, pa_sink_in
+     pa_sink_assert_ref(s);
+     for (j = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); j; j = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) {
+-        bool corked, muted, corked_here;
++        bool corked, corked_here;
+         const char *role;
+         if (j == ignore)
+@@ -119,10 +119,9 @@ static inline void apply_cork_to_sink(struct userdata *u, pa_sink *s, pa_sink_in
+             continue;
+         corked = (pa_sink_input_get_state(j) == PA_SINK_INPUT_CORKED);
+-        muted = pa_sink_input_get_mute(j);
+         corked_here = !!pa_hashmap_get(u->cork_state, j);
+-        if (cork && !corked && !muted) {
++        if (cork && !corked && !j->muted) {
+             pa_log_debug("Found a '%s' stream that should be corked/muted.", cork_role);
+             if (!corked_here)
+                 pa_hashmap_put(u->cork_state, j, PA_INT_TO_PTR(1));
+@@ -131,9 +130,9 @@ static inline void apply_cork_to_sink(struct userdata *u, pa_sink *s, pa_sink_in
+         } else if (!cork) {
+             pa_hashmap_remove(u->cork_state, j);
+-            if (corked_here && (corked || muted)) {
++            if (corked_here && (corked || j->muted)) {
+                 pa_log_debug("Found a '%s' stream that should be uncorked/unmuted.", cork_role);
+-                if (muted)
++                if (j->muted)
+                     pa_sink_input_set_mute(j, false, false);
+                 if (corked)
+                     pa_sink_input_send_event(j, PA_STREAM_EVENT_REQUEST_UNCORK, NULL);
+diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c
+index 68968c9..4fc5645 100644
+--- a/src/modules/module-stream-restore.c
++++ b/src/modules/module-stream-restore.c
+@@ -1303,7 +1303,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
+         }
+         if (sink_input->save_muted) {
+-            entry->muted = pa_sink_input_get_mute(sink_input);
++            entry->muted = sink_input->muted;
+             entry->muted_valid = true;
+             mute_updated = !created_new_entry && (!old->muted_valid || entry->muted != old->muted);
+@@ -1353,7 +1353,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
+         }
+         if (source_output->save_muted) {
+-            entry->muted = pa_source_output_get_mute(source_output);
++            entry->muted = source_output->muted;
+             entry->muted_valid = true;
+             mute_updated = !created_new_entry && (!old->muted_valid || entry->muted != old->muted);
+diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c
+index c7db0a6..2992ae8 100644
+--- a/src/pulsecore/cli-text.c
++++ b/src/pulsecore/cli-text.c
+@@ -536,7 +536,7 @@ char *pa_source_output_list_to_string(pa_core *c) {
+             state_table[pa_source_output_get_state(o)],
+             o->source->index, o->source->name,
+             volume_str,
+-            pa_yes_no(pa_source_output_get_mute(o)),
++            pa_yes_no(o->muted),
+             (double) pa_source_output_get_latency(o, NULL) / PA_USEC_PER_MSEC,
+             clt,
+             pa_sample_spec_snprint(ss, sizeof(ss), &o->sample_spec),
+@@ -634,7 +634,7 @@ char *pa_sink_input_list_to_string(pa_core *c) {
+             state_table[pa_sink_input_get_state(i)],
+             i->sink->index, i->sink->name,
+             volume_str,
+-            pa_yes_no(pa_sink_input_get_mute(i)),
++            pa_yes_no(i->muted),
+             (double) pa_sink_input_get_latency(i, NULL) / PA_USEC_PER_MSEC,
+             clt,
+             pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec),
+diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
+index 4304cd4..21e02fe 100644
+--- a/src/pulsecore/protocol-native.c
++++ b/src/pulsecore/protocol-native.c
+@@ -3378,7 +3378,7 @@ static void sink_input_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t,
+     pa_tagstruct_puts(t, pa_resample_method_to_string(pa_sink_input_get_resample_method(s)));
+     pa_tagstruct_puts(t, s->driver);
+     if (c->version >= 11)
+-        pa_tagstruct_put_boolean(t, pa_sink_input_get_mute(s));
++        pa_tagstruct_put_boolean(t, s->muted);
+     if (c->version >= 13)
+         pa_tagstruct_put_proplist(t, s->proplist);
+     if (c->version >= 19)
+@@ -3425,7 +3425,7 @@ static void source_output_fill_tagstruct(pa_native_connection *c, pa_tagstruct *
+         pa_tagstruct_put_boolean(t, (pa_source_output_get_state(s) == PA_SOURCE_OUTPUT_CORKED));
+     if (c->version >= 22) {
+         pa_tagstruct_put_cvolume(t, &v);
+-        pa_tagstruct_put_boolean(t, pa_source_output_get_mute(s));
++        pa_tagstruct_put_boolean(t, s->muted);
+         pa_tagstruct_put_boolean(t, has_volume);
+         pa_tagstruct_put_boolean(t, s->volume_writable);
+         pa_tagstruct_put_format_info(t, s->format);
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index f706acc..a274620 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -1485,15 +1485,6 @@ void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save) {
+     pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ }
+-/* Called from main context */
+-bool pa_sink_input_get_mute(pa_sink_input *i) {
+-    pa_sink_input_assert_ref(i);
+-    pa_assert_ctl_context();
+-    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+-
+-    return i->muted;
+-}
+-
+ /* Called from main thread */
+ void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p) {
+     pa_sink_input_assert_ref(i);
+diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
+index 4e7b229..1bd3eee 100644
+--- a/src/pulsecore/sink-input.h
++++ b/src/pulsecore/sink-input.h
+@@ -387,7 +387,6 @@ int pa_sink_input_remove_volume_factor(pa_sink_input *i, const char *key);
+ pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, bool absolute);
+ void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save);
+-bool pa_sink_input_get_mute(pa_sink_input *i);
+ void pa_sink_input_set_volume_ramp(pa_sink_input *i, const pa_cvolume_ramp *ramp, bool send_msg, bool save);
+diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
+index bb89384..d3888df 100644
+--- a/src/pulsecore/source-output.c
++++ b/src/pulsecore/source-output.c
+@@ -1084,15 +1084,6 @@ void pa_source_output_set_mute(pa_source_output *o, bool mute, bool save) {
+     pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ }
+-/* Called from main context */
+-bool pa_source_output_get_mute(pa_source_output *o) {
+-    pa_source_output_assert_ref(o);
+-    pa_assert_ctl_context();
+-    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+-
+-    return o->muted;
+-}
+-
+ /* Called from main thread */
+ void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p) {
+     pa_source_output_assert_ref(o);
+diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
+index 27d6fd4..73170d3 100644
+--- a/src/pulsecore/source-output.h
++++ b/src/pulsecore/source-output.h
+@@ -318,7 +318,6 @@ void pa_source_output_set_volume(pa_source_output *o, const pa_cvolume *volume,
+ pa_cvolume *pa_source_output_get_volume(pa_source_output *o, pa_cvolume *volume, bool absolute);
+ void pa_source_output_set_mute(pa_source_output *o, bool mute, bool save);
+-bool pa_source_output_get_mute(pa_source_output *o);
+ void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0071-solaris-tunnel-Remove-some-redundant-boolean-convers.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0071-solaris-tunnel-Remove-some-redundant-boolean-convers.patch
new file mode 100644 (file)
index 0000000..4b4acfe
--- /dev/null
@@ -0,0 +1,37 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 14 Apr 2014 15:34:57 +0300
+Subject: solaris, tunnel: Remove some redundant boolean conversions
+
+Change-Id: Ibc922f8455a3ccb6c71289e70cd474464930643e
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/modules/module-solaris.c | 2 +-
+ src/modules/module-tunnel.c  | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c
+index 71a98e9..4f11000 100644
+--- a/src/modules/module-solaris.c
++++ b/src/modules/module-solaris.c
+@@ -564,7 +564,7 @@ static void sink_set_mute(pa_sink *s) {
+     if (u->fd >= 0) {
+         AUDIO_INITINFO(&info);
+-        info.output_muted = !!s->muted;
++        info.output_muted = s->muted;
+         if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0)
+             pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
+diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
+index 1ddfd25..0fc3d73 100644
+--- a/src/modules/module-tunnel.c
++++ b/src/modules/module-tunnel.c
+@@ -1906,7 +1906,7 @@ static void sink_set_mute(pa_sink *sink) {
+     pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_MUTE);
+     pa_tagstruct_putu32(t, u->ctag++);
+     pa_tagstruct_putu32(t, u->device_index);
+-    pa_tagstruct_put_boolean(t, !!sink->muted);
++    pa_tagstruct_put_boolean(t, sink->muted);
+     pa_pstream_send_tagstruct(u->pstream, t);
+ }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0072-sink-source-Add-hooks-for-volume-changes.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0072-sink-source-Add-hooks-for-volume-changes.patch
new file mode 100644 (file)
index 0000000..08fbc83
--- /dev/null
@@ -0,0 +1,52 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Sun, 9 Mar 2014 13:36:04 +0200
+Subject: sink, source: Add hooks for volume changes
+
+Change-Id: Id4389a38e601dee3f84d7fad0583a7dc87108e87
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/core.h   | 2 ++
+ src/pulsecore/sink.c   | 1 +
+ src/pulsecore/source.c | 1 +
+ 3 files changed, 4 insertions(+)
+
+diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
+index e1cd18f..85f1b81 100644
+--- a/src/pulsecore/core.h
++++ b/src/pulsecore/core.h
+@@ -76,6 +76,7 @@ typedef enum pa_core_hook {
+     PA_CORE_HOOK_SINK_PROPLIST_CHANGED,
+     PA_CORE_HOOK_SINK_PORT_CHANGED,
+     PA_CORE_HOOK_SINK_FLAGS_CHANGED,
++    PA_CORE_HOOK_SINK_VOLUME_CHANGED,
+     PA_CORE_HOOK_SOURCE_NEW,
+     PA_CORE_HOOK_SOURCE_FIXATE,
+     PA_CORE_HOOK_SOURCE_PUT,
+@@ -85,6 +86,7 @@ typedef enum pa_core_hook {
+     PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED,
+     PA_CORE_HOOK_SOURCE_PORT_CHANGED,
+     PA_CORE_HOOK_SOURCE_FLAGS_CHANGED,
++    PA_CORE_HOOK_SOURCE_VOLUME_CHANGED,
+     PA_CORE_HOOK_SINK_INPUT_NEW,
+     PA_CORE_HOOK_SINK_INPUT_FIXATE,
+     PA_CORE_HOOK_SINK_INPUT_PUT,
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index b64001b..f8e5449 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -3907,4 +3907,5 @@ void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume) {
+                                             s->flags & PA_SINK_DECIBEL_VOLUME));
+     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
++    pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], s);
+ }
+diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
+index 24e8be9..8a43708 100644
+--- a/src/pulsecore/source.c
++++ b/src/pulsecore/source.c
+@@ -2900,4 +2900,5 @@ void pa_source_set_reference_volume_direct(pa_source *s, const pa_cvolume *volum
+                                             s->flags & PA_SOURCE_DECIBEL_VOLUME));
+     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
++    pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_VOLUME_CHANGED], s);
+ }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0073-sink-input-source-output-Add-hooks-for-volume-change.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0073-sink-input-source-output-Add-hooks-for-volume-change.patch
new file mode 100644 (file)
index 0000000..9cf1c67
--- /dev/null
@@ -0,0 +1,52 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 7 Apr 2014 14:22:43 +0300
+Subject: sink-input, source-output: Add hooks for volume changes
+
+Change-Id: I89c6f2934762caa2c49c70c0446c14d0de58a10e
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/core.h          | 2 ++
+ src/pulsecore/sink-input.c    | 1 +
+ src/pulsecore/source-output.c | 1 +
+ 3 files changed, 4 insertions(+)
+
+diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
+index 85f1b81..a839898 100644
+--- a/src/pulsecore/core.h
++++ b/src/pulsecore/core.h
+@@ -97,6 +97,7 @@ typedef enum pa_core_hook {
+     PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL,
+     PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED,
+     PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED,
++    PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED,
+     PA_CORE_HOOK_SINK_INPUT_SEND_EVENT,
+     PA_CORE_HOOK_SOURCE_OUTPUT_NEW,
+     PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE,
+@@ -108,6 +109,7 @@ typedef enum pa_core_hook {
+     PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL,
+     PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED,
+     PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED,
++    PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED,
+     PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT,
+     PA_CORE_HOOK_CLIENT_NEW,
+     PA_CORE_HOOK_CLIENT_PUT,
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index a274620..3024539 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -2326,4 +2326,5 @@ void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume)
+         i->volume_changed(i);
+     pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
++    pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED], i);
+ }
+diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
+index d3888df..18a1478 100644
+--- a/src/pulsecore/source-output.c
++++ b/src/pulsecore/source-output.c
+@@ -1692,4 +1692,5 @@ void pa_source_output_set_volume_direct(pa_source_output *o, const pa_cvolume *v
+         o->volume_changed(o);
+     pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
++    pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED], o);
+ }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0074-sink-source-Add-hooks-for-mute-changes.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0074-sink-source-Add-hooks-for-mute-changes.patch
new file mode 100644 (file)
index 0000000..ddd8514
--- /dev/null
@@ -0,0 +1,56 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Tue, 15 Apr 2014 11:10:24 +0300
+Subject: sink, source: Add hooks for mute changes
+
+Change-Id: I1203c1199fea0e93f1a61391695b12ab8af66180
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/core.h   | 2 ++
+ src/pulsecore/sink.c   | 1 +
+ src/pulsecore/source.c | 1 +
+ 3 files changed, 4 insertions(+)
+
+diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
+index a839898..af7fa50 100644
+--- a/src/pulsecore/core.h
++++ b/src/pulsecore/core.h
+@@ -77,6 +77,7 @@ typedef enum pa_core_hook {
+     PA_CORE_HOOK_SINK_PORT_CHANGED,
+     PA_CORE_HOOK_SINK_FLAGS_CHANGED,
+     PA_CORE_HOOK_SINK_VOLUME_CHANGED,
++    PA_CORE_HOOK_SINK_MUTE_CHANGED,
+     PA_CORE_HOOK_SOURCE_NEW,
+     PA_CORE_HOOK_SOURCE_FIXATE,
+     PA_CORE_HOOK_SOURCE_PUT,
+@@ -87,6 +88,7 @@ typedef enum pa_core_hook {
+     PA_CORE_HOOK_SOURCE_PORT_CHANGED,
+     PA_CORE_HOOK_SOURCE_FLAGS_CHANGED,
+     PA_CORE_HOOK_SOURCE_VOLUME_CHANGED,
++    PA_CORE_HOOK_SOURCE_MUTE_CHANGED,
+     PA_CORE_HOOK_SINK_INPUT_NEW,
+     PA_CORE_HOOK_SINK_INPUT_FIXATE,
+     PA_CORE_HOOK_SINK_INPUT_PUT,
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index f8e5449..695e471 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -2308,6 +2308,7 @@ void pa_sink_set_mute(pa_sink *s, bool mute, bool save) {
+     pa_log_debug("The mute of sink %s changed from %s to %s.", s->name, pa_yes_no(old_muted), pa_yes_no(mute));
+     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
+     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
++    pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_MUTE_CHANGED], s);
+ }
+ /* Called from main thread */
+diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
+index 8a43708..0fddfaa 100644
+--- a/src/pulsecore/source.c
++++ b/src/pulsecore/source.c
+@@ -1826,6 +1826,7 @@ void pa_source_set_mute(pa_source *s, bool mute, bool save) {
+     pa_log_debug("The mute of source %s changed from %s to %s.", s->name, pa_yes_no(old_muted), pa_yes_no(mute));
+     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
+     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
++    pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_MUTE_CHANGED], s);
+ }
+ /* Called from main thread */
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0075-sink-input-source-output-Add-hooks-for-mute-changes.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0075-sink-input-source-output-Add-hooks-for-mute-changes.patch
new file mode 100644 (file)
index 0000000..2607c14
--- /dev/null
@@ -0,0 +1,56 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Tue, 15 Apr 2014 11:27:53 +0300
+Subject: sink-input, source-output: Add hooks for mute changes
+
+Change-Id: I256cfa27ffa6addb35640266b73f1fe07a483203
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/core.h          | 2 ++
+ src/pulsecore/sink-input.c    | 1 +
+ src/pulsecore/source-output.c | 1 +
+ 3 files changed, 4 insertions(+)
+
+diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
+index af7fa50..1f042b9 100644
+--- a/src/pulsecore/core.h
++++ b/src/pulsecore/core.h
+@@ -100,6 +100,7 @@ typedef enum pa_core_hook {
+     PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED,
+     PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED,
+     PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED,
++    PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED,
+     PA_CORE_HOOK_SINK_INPUT_SEND_EVENT,
+     PA_CORE_HOOK_SOURCE_OUTPUT_NEW,
+     PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE,
+@@ -112,6 +113,7 @@ typedef enum pa_core_hook {
+     PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED,
+     PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED,
+     PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED,
++    PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED,
+     PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT,
+     PA_CORE_HOOK_CLIENT_NEW,
+     PA_CORE_HOOK_CLIENT_PUT,
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index 3024539..9d13269 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -1483,6 +1483,7 @@ void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save) {
+         i->mute_changed(i);
+     pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
++    pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], i);
+ }
+ /* Called from main thread */
+diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
+index 18a1478..d3d15f1 100644
+--- a/src/pulsecore/source-output.c
++++ b/src/pulsecore/source-output.c
+@@ -1082,6 +1082,7 @@ void pa_source_output_set_mute(pa_source_output *o, bool mute, bool save) {
+         o->mute_changed(o);
+     pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
++    pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED], o);
+ }
+ /* Called from main thread */
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0076-sink-Link-monitor-source-before-activating-port.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0076-sink-Link-monitor-source-before-activating-port.patch
new file mode 100644 (file)
index 0000000..e2de6d6
--- /dev/null
@@ -0,0 +1,30 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Fri, 9 May 2014 11:25:28 +0300
+Subject: sink: Link monitor source before activating port
+
+The port activation hook callbacks may want to use the monitor source.
+
+Change-Id: I5d5c51171a78162dacb3286983cb560001e79ba1
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/pulsecore/sink.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index 695e471..191b560 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -669,11 +669,11 @@ void pa_sink_put(pa_sink* s) {
+     else
+         pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0);
++    pa_source_put(s->monitor_source);
++
+     if (s->active_port)
+         pa_device_port_active_changed(s->active_port, true);
+-    pa_source_put(s->monitor_source);
+-
+     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_NEW, s->index);
+     pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PUT], s);
+ }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0077-context-extension-Add-the-pa_extension-class.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0077-context-extension-Add-the-pa_extension-class.patch
new file mode 100644 (file)
index 0000000..a2a5141
--- /dev/null
@@ -0,0 +1,338 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Tue, 4 Mar 2014 15:03:05 +0200
+Subject: context, extension: Add the pa_extension class
+
+pa_extension is an abstraction layer that allows pa_context to manage
+the extensions without needing any extension-specific code. This patch
+only implements the pa_extension base class, the class isn't used yet
+by any actual extensions.
+
+Change-Id: I457b3d0b674b4cfd1d38452d8f8cb51cf6b7b533
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/Makefile.am       |   1 +
+ src/pulse/context.c   |  48 +++++++++++++++++++++++-
+ src/pulse/extension.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ src/pulse/extension.h |  63 +++++++++++++++++++++++++++++++
+ src/pulse/internal.h  |   6 +++
+ 5 files changed, 217 insertions(+), 1 deletion(-)
+ create mode 100644 src/pulse/extension.c
+ create mode 100644 src/pulse/extension.h
+
+diff --git a/src/Makefile.am b/src/Makefile.am
+index fe6cc53..22b9b81 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -813,6 +813,7 @@ libpulse_la_SOURCES = \
+               pulse/def.h \
+               pulse/direction.c pulse/direction.h \
+               pulse/error.c pulse/error.h \
++              pulse/extension.c pulse/extension.h \
+               pulse/ext-device-manager.c pulse/ext-device-manager.h \
+               pulse/ext-device-restore.c pulse/ext-device-restore.h \
+               pulse/ext-stream-restore.c pulse/ext-stream-restore.h \
+diff --git a/src/pulse/context.c b/src/pulse/context.c
+index b8688f2..9c9c3d9 100644
+--- a/src/pulse/context.c
++++ b/src/pulse/context.c
+@@ -186,14 +186,20 @@ pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *
+         }
+     }
++    c->extensions = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
++
+     return c;
+ }
+ static void context_unlink(pa_context *c) {
++    pa_extension *extension;
+     pa_stream *s;
+     pa_assert(c);
++    while ((extension = pa_hashmap_first(c->extensions)))
++        pa_extension_kill(extension);
++
+     s = c->streams ? pa_stream_ref(c->streams) : NULL;
+     while (s) {
+         pa_stream *n = s->next ? pa_stream_ref(s->next) : NULL;
+@@ -280,6 +286,9 @@ void pa_context_unref(pa_context *c) {
+ }
+ void pa_context_set_state(pa_context *c, pa_context_state_t st) {
++    pa_extension *extension;
++    void *state;
++
+     pa_assert(c);
+     pa_assert(PA_REFCNT_VALUE(c) >= 1);
+@@ -290,6 +299,12 @@ void pa_context_set_state(pa_context *c, pa_context_state_t st) {
+     c->state = st;
++    PA_HASHMAP_FOREACH(extension, c->extensions, state)
++        pa_extension_context_state_changed(extension, 1);
++
++    PA_HASHMAP_FOREACH(extension, c->extensions, state)
++        pa_extension_context_state_changed(extension, 2);
++
+     if (c->state_callback)
+         c->state_callback(c, c->state_userdata);
+@@ -1338,6 +1353,7 @@ void pa_command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_t
+     pa_context *c = userdata;
+     uint32_t idx;
+     const char *name;
++    pa_extension *extension;
+     pa_assert(pd);
+     pa_assert(command == PA_COMMAND_EXTENSION);
+@@ -1366,7 +1382,16 @@ void pa_command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_t
+         pa_ext_stream_restore_command(c, tag, t);
+     else if (pa_streq(name, "module-node-manager"))
+         pa_ext_node_manager_command(c, tag, t);
+-    else
++    else if ((extension = pa_context_get_extension(c, name))) {
++        uint32_t subcommand;
++
++        if (pa_tagstruct_getu32(t, &subcommand) < 0) {
++            pa_context_fail(c, PA_ERR_PROTOCOL);
++            goto finish;
++        }
++
++        pa_extension_process_command(extension, subcommand, tag, t);
++    } else
+         pa_log(_("Received message for unknown extension '%s'"), name);
+ finish:
+@@ -1464,3 +1489,24 @@ int pa_context_load_cookie_from_file(pa_context *c, const char *cookie_file_path
+     return pa_client_conf_load_cookie_from_file(c->conf, cookie_file_path);
+ }
++
++pa_extension *pa_context_get_extension(pa_context *context, const char *name) {
++    pa_assert(context);
++    pa_assert(name);
++
++    return pa_hashmap_get(context->extensions, name);
++}
++
++void pa_context_add_extension(pa_context *context, pa_extension *extension) {
++    pa_assert(context);
++    pa_assert(extension);
++
++    pa_assert_se(pa_hashmap_put(context->extensions, extension->name, extension) >= 0);
++}
++
++int pa_context_remove_extension(pa_context *context, pa_extension *extension) {
++    pa_assert(context);
++    pa_assert(extension);
++
++    return pa_hashmap_remove(context->extensions, extension->name) ? 0 : -1;
++}
+diff --git a/src/pulse/extension.c b/src/pulse/extension.c
+new file mode 100644
+index 0000000..17e7e6c
+--- /dev/null
++++ b/src/pulse/extension.c
+@@ -0,0 +1,100 @@
++/***
++  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 "extension.h"
++
++#include <pulsecore/macro.h>
++
++#include <pulse/internal.h>
++#include <pulse/xmalloc.h>
++
++pa_extension *pa_extension_new(pa_context *context, const char *name) {
++    pa_extension *extension = NULL;
++
++    pa_assert(context);
++    pa_assert(name);
++
++    extension = pa_xnew0(pa_extension, 1);
++    extension->context = context;
++    extension->name = pa_xstrdup(name);
++
++    return extension;
++}
++
++void pa_extension_put(pa_extension *extension) {
++    pa_assert(extension);
++    pa_assert(extension->kill);
++
++    pa_context_add_extension(extension->context, extension);
++}
++
++static void extension_unlink(pa_extension *extension) {
++    pa_assert(extension);
++
++    if (extension->unlinked)
++        return;
++
++    extension->unlinked = true;
++
++    pa_context_remove_extension(extension->context, extension);
++}
++
++void pa_extension_free(pa_extension *extension) {
++    pa_assert(extension);
++
++    extension_unlink(extension);
++
++    pa_xfree(extension->name);
++    pa_xfree(extension);
++}
++
++void pa_extension_context_state_changed(pa_extension *extension, unsigned phase) {
++    pa_assert(extension);
++    pa_assert(phase == 1 || phase == 2);
++
++    if (extension->context_state_changed)
++        extension->context_state_changed(extension, phase);
++}
++
++void pa_extension_kill(pa_extension *extension) {
++    pa_assert(extension);
++
++    if (extension->unlinked)
++        return;
++
++    extension->kill(extension);
++}
++
++void pa_extension_process_command(pa_extension *extension, uint32_t command, uint32_t tag, pa_tagstruct *tagstruct) {
++    pa_assert(extension);
++    pa_assert(tagstruct);
++
++    if (extension->process_command)
++        extension->process_command(extension, command, tag, tagstruct);
++    else {
++        pa_log("Unexpected command for extension %s: %u", extension->name, command);
++        pa_context_fail(extension->context, PA_ERR_PROTOCOL);
++    }
++}
+diff --git a/src/pulse/extension.h b/src/pulse/extension.h
+new file mode 100644
+index 0000000..cadc267
+--- /dev/null
++++ b/src/pulse/extension.h
+@@ -0,0 +1,63 @@
++#ifndef fooextensionhfoo
++#define fooextensionhfoo
++
++/***
++  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/context.h>
++
++#include <pulsecore/tagstruct.h>
++
++#include <stdbool.h>
++
++typedef struct pa_extension pa_extension;
++
++struct pa_extension {
++    pa_context *context;
++    char *name;
++    bool unlinked;
++
++    /* This is called when the context state changes. The callback is called
++     * twice for each state change, first with phase = 1 and then with
++     * phase = 2. In the first phase the extension should update its internal
++     * state without calling any application callbacks. In the second phase it
++     * should call the application callbacks (if any). May be NULL. */
++    void (*context_state_changed)(pa_extension *extension, unsigned phase);
++
++    /* Called from pa_extension_kill(). May not be NULL. */
++    void (*kill)(pa_extension *extension);
++
++    /* Called from pa_extension_process_command(). May be NULL, if the
++     * extension doesn't expect any commands from the server. */
++    void (*process_command)(pa_extension *extension, uint32_t command, uint32_t tag, pa_tagstruct *tagstruct);
++
++    void *userdata;
++};
++
++pa_extension *pa_extension_new(pa_context *context, const char *name);
++void pa_extension_put(pa_extension *extension);
++void pa_extension_free(pa_extension *extension);
++
++void pa_extension_context_state_changed(pa_extension *extension, unsigned phase);
++void pa_extension_kill(pa_extension *extension);
++void pa_extension_process_command(pa_extension *extension, uint32_t command, uint32_t tag, pa_tagstruct *tagstruct);
++
++#endif
+diff --git a/src/pulse/internal.h b/src/pulse/internal.h
+index 61095d0..1428fb8 100644
+--- a/src/pulse/internal.h
++++ b/src/pulse/internal.h
+@@ -23,6 +23,7 @@
+   USA.
+ ***/
++#include <pulse/extension.h>
+ #include <pulse/mainloop-api.h>
+ #include <pulse/context.h>
+ #include <pulse/stream.h>
+@@ -103,6 +104,8 @@ struct pa_context {
+     uint32_t client_index;
++    pa_hashmap *extensions; /* extension name -> pa_extension */
++
+     /* Extension specific data */
+     struct {
+         pa_ext_device_manager_subscribe_cb_t callback;
+@@ -269,6 +272,9 @@ int pa_context_set_error(pa_context *c, int error);
+ void pa_context_set_state(pa_context *c, pa_context_state_t st);
+ int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t, bool fail);
+ pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, void (*internal_callback)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata), void (*cb)(void), void *userdata);
++pa_extension *pa_context_get_extension(pa_context *context, const char *name);
++void pa_context_add_extension(pa_context *context, pa_extension *extension);
++int pa_context_remove_extension(pa_context *context, pa_extension *extension);
+ void pa_stream_set_state(pa_stream *s, pa_stream_state_t st);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0078-volume-api-Add-libvolume-api.so.patch.gz b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0078-volume-api-Add-libvolume-api.so.patch.gz
new file mode 100644 (file)
index 0000000..465ef38
Binary files /dev/null and b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0078-volume-api-Add-libvolume-api.so.patch.gz differ
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0079-Add-module-volume-api-and-the-related-client-API.patch.gz b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0079-Add-module-volume-api-and-the-related-client-API.patch.gz
new file mode 100644 (file)
index 0000000..d3e3ee7
Binary files /dev/null and b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0079-Add-module-volume-api-and-the-related-client-API.patch.gz differ
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0080-pactl-Add-support-for-the-new-volume-API.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0080-pactl-Add-support-for-the-new-volume-API.patch
new file mode 100644 (file)
index 0000000..3fd8311
--- /dev/null
@@ -0,0 +1,963 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Wed, 21 May 2014 14:05:47 +0300
+Subject: pactl: Add support for the new volume API
+
+Change-Id: I2bb6625c1cd575366388ec8dc3dd4fd2097c9a4a
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ man/pactl.1.xml.in |  50 +++-
+ src/utils/pactl.c  | 727 +++++++++++++++++++++++++++++++++++++++++++++++++++--
+ 2 files changed, 759 insertions(+), 18 deletions(-)
+
+diff --git a/man/pactl.1.xml.in b/man/pactl.1.xml.in
+index 29071b3..cd54e4c 100644
+--- a/man/pactl.1.xml.in
++++ b/man/pactl.1.xml.in
+@@ -80,9 +80,12 @@ USA.
+     <option>
+       <p><opt>list</opt> [<arg>short</arg>] [<arg>TYPE</arg>]</p>
+-      <optdesc><p>Dump all currently loaded modules, available sinks, sources, streams, etc.  <arg>TYPE</arg> must be one of:
+-      modules, sinks, sources, sink-inputs, source-outputs, clients, samples, cards.  If not specified, all info is listed.  If
+-      short is given, output is in a tabular format, for easy parsing by scripts.</p></optdesc>
++      <optdesc><p>Dump all currently loaded modules, available sinks, sources,
++      streams, etc.  <arg>TYPE</arg> must be one of: modules, sinks, sources,
++      sink-inputs, source-outputs, clients, samples, cards, volume-controls,
++      mute-controls, devices, streams, audio-groups. If not specified, all info
++      is listed. If short is given, output is in a tabular format, for easy
++      parsing by scripts. </p></optdesc>
+     </option>
+     <option>
+@@ -244,7 +247,46 @@ USA.
+       <arg>FORMATS</arg> is specified as a semi-colon (;) separated list of formats in the form
+       'encoding[, key1=value1, key2=value2, ...]' (for example, AC3 at 32000, 44100 and 48000 Hz would be specified as
+       'ac3-iec61937, format.rate = "[ 32000, 44100, 48000 ]"').
+-      </p></optdesc> </option>
++      </p></optdesc>
++    </option>
++
++    <option>
++      <p><opt>set-volume-control-volume</opt> <arg>CONTROL</arg>
++        <arg>VOLUME</arg> <arg>[BALANCE ...]</arg>
++      </p>
++      <optdesc><p>Set the overall volume of the specified volume control
++        (identified by its name or index). <arg>VOLUME</arg> can be specified
++        as an integer (e.g. 2000, 16384), a linear factor (e.g. 0.4, 1.100), a
++        percentage (e.g.  10%, 100%) or a decibel value (e.g. 0dB, 20dB).  If
++        the volume specification start with a + or - the volume adjustment will
++        be relative to the current source output volume. Optionally, you can
++        also provide the channel balance (see also set-volume-control-balance).
++      </p></optdesc>
++    </option>
++
++    <option>
++      <p><opt>set-volume-control-balance</opt> <arg>CONTROL</arg>
++        <arg>BALANCE ...</arg>
++      </p>
++      <optdesc><p>Set the channel balance of the specified volume control
++        (identified by its name or index). The balance is given as separate
++        values for each channel. The balance values must be between 0.0 and
++        1.0. The number of values must match the volume control's channel map.
++      </p></optdesc>
++    </option>
++
++    <option>
++      <p>
++        <opt>set-mute-control-mute</opt> <arg>CONTROL</arg> <arg>1|0|toggle
++        </arg>
++      </p>
++      <optdesc><p>
++        Set the mute state of the specified mute control (identified by its
++        name or index). If the mute value is "toggle", then the mute control
++        will be muted if it was previously unmuted, and unmuted if it was
++        previously muted.
++      </p></optdesc>
++    </option>
+     <option>
+       <p><opt>subscribe</opt></p>
+diff --git a/src/utils/pactl.c b/src/utils/pactl.c
+index 958d700..f947681 100644
+--- a/src/utils/pactl.c
++++ b/src/utils/pactl.c
+@@ -39,6 +39,7 @@
+ #include <pulse/pulseaudio.h>
+ #include <pulse/ext-device-restore.h>
+ #include <pulse/ext-node-manager.h>
++#include <pulse/ext-volume-api.h>
+ #include <pulsecore/i18n.h>
+ #include <pulsecore/macro.h>
+@@ -59,7 +60,9 @@ static char
+     *card_name = NULL,
+     *profile_name = NULL,
+     *port_name = NULL,
+-    *formats = NULL;
++    *formats = NULL,
++    *volume_control_name = NULL,
++    *mute_control_name = NULL;
+ static uint32_t
+     sink_input_idx = PA_INVALID_INDEX,
+@@ -101,6 +104,14 @@ static bool nl = false;
+ static uint32_t src_node_id;
+ static uint32_t dst_node_id;
+ static uint32_t conn_id;
++bool volume_api_connected = false;
++pa_ext_volume_api_bvolume bvolume;
++bool volume_valid = false;
++bool balance_valid = false;
++uint32_t main_output_volume_control = PA_INVALID_INDEX;
++uint32_t main_input_volume_control = PA_INVALID_INDEX;
++uint32_t main_output_mute_control = PA_INVALID_INDEX;
++uint32_t main_input_mute_control = PA_INVALID_INDEX;
+ static enum {
+     NONE,
+@@ -132,6 +143,8 @@ static enum {
+     SET_SOURCE_OUTPUT_MUTE,
+     SET_SINK_FORMATS,
+     SET_PORT_LATENCY_OFFSET,
++    SET_VOLUME_CONTROL_VOLUME,
++    SET_MUTE_CONTROL_MUTE,
+     SUBSCRIBE,
+     NODE_CONNECT,
+     NODE_DISCONNECT
+@@ -838,18 +851,18 @@ static void index_callback(pa_context *c, uint32_t idx, void *userdata) {
+     complete_action();
+ }
+-static void volume_relative_adjust(pa_cvolume *cv) {
++static void volume_relative_adjust(pa_cvolume *cv, pa_volume_t adjustment) {
+     pa_assert((volume_flags & VOL_RELATIVE) == VOL_RELATIVE);
+     /* Relative volume change is additive in case of UINT or PERCENT
+      * and multiplicative for LINEAR or DECIBEL */
+     if ((volume_flags & 0x0F) == VOL_UINT || (volume_flags & 0x0F) == VOL_PERCENT) {
+         pa_volume_t v = pa_cvolume_avg(cv);
+-        v = v + volume < PA_VOLUME_NORM ? PA_VOLUME_MUTED : v + volume - PA_VOLUME_NORM;
++        v = v + adjustment < PA_VOLUME_NORM ? PA_VOLUME_MUTED : v + adjustment - PA_VOLUME_NORM;
+         pa_cvolume_set(cv, 1, v);
+     }
+     if ((volume_flags & 0x0F) == VOL_LINEAR || (volume_flags & 0x0F) == VOL_DECIBEL) {
+-        pa_sw_cvolume_multiply_scalar(cv, cv, volume);
++        pa_sw_cvolume_multiply_scalar(cv, cv, adjustment);
+     }
+ }
+@@ -893,7 +906,7 @@ static void get_sink_volume_callback(pa_context *c, const pa_sink_info *i, int i
+     pa_assert(i);
+     cv = i->volume;
+-    volume_relative_adjust(&cv);
++    volume_relative_adjust(&cv, volume);
+     pa_operation_unref(pa_context_set_sink_volume_by_name(c, sink_name, &cv, simple_callback, NULL));
+ }
+@@ -912,7 +925,7 @@ static void get_source_volume_callback(pa_context *c, const pa_source_info *i, i
+     pa_assert(i);
+     cv = i->volume;
+-    volume_relative_adjust(&cv);
++    volume_relative_adjust(&cv, volume);
+     pa_operation_unref(pa_context_set_source_volume_by_name(c, source_name, &cv, simple_callback, NULL));
+ }
+@@ -931,7 +944,7 @@ static void get_sink_input_volume_callback(pa_context *c, const pa_sink_input_in
+     pa_assert(i);
+     cv = i->volume;
+-    volume_relative_adjust(&cv);
++    volume_relative_adjust(&cv, volume);
+     pa_operation_unref(pa_context_set_sink_input_volume(c, sink_input_idx, &cv, simple_callback, NULL));
+ }
+@@ -950,7 +963,7 @@ static void get_source_output_volume_callback(pa_context *c, const pa_source_out
+     pa_assert(o);
+     cv = o->volume;
+-    volume_relative_adjust(&cv);
++    volume_relative_adjust(&cv, volume);
+     pa_operation_unref(pa_context_set_source_output_volume(c, source_output_idx, &cv, simple_callback, NULL));
+ }
+@@ -1189,6 +1202,575 @@ static void context_subscribe_callback(pa_context *c, pa_subscription_event_type
+     fflush(stdout);
+ }
++static void get_volume_control_info_callback(pa_context *c, const pa_ext_volume_api_volume_control_info *info,
++                                             int is_last, void *userdata) {
++    char volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX];
++    char balance_str[PA_EXT_VOLUME_API_BVOLUME_SNPRINT_BALANCE_MAX];
++    char *proplist_str;
++
++    pa_assert(c);
++
++    if (is_last < 0) {
++        pa_log(_("Failed to get volume control information: %s"), pa_strerror(pa_context_errno(c)));
++        quit(1);
++        return;
++    }
++
++    if (is_last) {
++        complete_action();
++        return;
++    }
++
++    pa_assert(info);
++
++    if (action == INFO) {
++        if (info->index == main_output_volume_control)
++            printf(_("Main output volume control: %s\n"), info->name);
++
++        if (info->index == main_input_volume_control)
++            printf(_("Main input volume control: %s\n"), info->name);
++
++        return;
++    }
++
++    if (action == SET_VOLUME_CONTROL_VOLUME) {
++        pa_ext_volume_api_bvolume bv;
++
++        if (balance_valid && bvolume.channel_map.channels != info->volume.channel_map.channels) {
++            pa_log(_("Incompatible number of channels, expected %u channels."), info->volume.channel_map.channels);
++            quit(1);
++        }
++
++        bv = info->volume;
++
++        if (volume_valid) {
++            if (volume_flags & VOL_RELATIVE) {
++                pa_cvolume cv;
++
++                pa_cvolume_set(&cv, 1, info->volume.volume);
++                volume_relative_adjust(&cv, bvolume.volume);
++                bv.volume = cv.values[0];
++            } else
++                bv.volume = bvolume.volume;
++        }
++
++        if (balance_valid)
++            memcpy(bv.balance, bvolume.balance, sizeof(bv.balance));
++
++        pa_operation_unref(pa_ext_volume_api_set_volume_control_volume_by_name(c, volume_control_name, &bv,
++                                                                               volume_valid, balance_valid,
++                                                                               simple_callback, NULL));
++        actions++;
++
++        return;
++    }
++
++    pa_assert(action == LIST);
++
++    if (nl && !short_list_format)
++        printf("\n");
++    nl = true;
++
++    if (short_list_format) {
++        printf("%u\t%s\t%u\n", info->index, info->name, info->volume.volume);
++        return;
++    }
++
++    pa_volume_snprint_verbose(volume_str, sizeof(volume_str), info->volume.volume, info->convertible_to_dB);
++    pa_ext_volume_api_bvolume_snprint_balance(balance_str, sizeof(balance_str), &info->volume);
++    proplist_str = pa_proplist_to_string_sep(info->proplist, "\n\t\t");
++
++    printf(_("Volume Control #%u\n"
++             "\tName: %s\n"
++             "\tDescription: %s\n"
++             "\tVolume: %s\n"
++             "\tBalance: %s\n"
++             "\tProperties: %s%s\n"),
++             info->index,
++             info->name,
++             info->description,
++             volume_str,
++             balance_str,
++             *proplist_str ? "\n\t\t" : _("(none)"),
++             proplist_str);
++
++    pa_xfree(proplist_str);
++}
++
++static void get_mute_control_info_callback(pa_context *c, const pa_ext_volume_api_mute_control_info *info, int is_last,
++                                           void *userdata) {
++    char *proplist_str;
++
++    pa_assert(c);
++
++    if (is_last < 0) {
++        pa_log(_("Failed to get mute control information: %s"), pa_strerror(pa_context_errno(c)));
++        quit(1);
++        return;
++    }
++
++    if (is_last) {
++        complete_action();
++        return;
++    }
++
++    pa_assert(info);
++
++    if (action == INFO) {
++        if (info->index == main_output_mute_control)
++            printf(_("Main output mute control: %s\n"), info->name);
++
++        if (info->index == main_input_mute_control)
++            printf(_("Main input mute control: %s\n"), info->name);
++
++        return;
++    }
++
++    if (action == SET_MUTE_CONTROL_MUTE) {
++        pa_operation_unref(pa_ext_volume_api_set_mute_control_mute_by_index(c, info->index, info->mute ? false : true,
++                                                                                   simple_callback, NULL));
++        actions++;
++        return;
++    }
++
++    pa_assert(action == LIST);
++
++    if (nl && !short_list_format)
++        printf("\n");
++    nl = true;
++
++    if (short_list_format) {
++        printf("%u\t%s\t%s\n", info->index, info->name, pa_yes_no(info->mute));
++        return;
++    }
++
++    proplist_str = pa_proplist_to_string_sep(info->proplist, "\n\t\t");
++
++    printf(_("Mute Control #%u\n"
++             "\tName: %s\n"
++             "\tDescription: %s\n"
++             "\tMute: %s\n"
++             "\tProperties: %s%s\n"),
++             info->index,
++             info->name,
++             info->description,
++             pa_yes_no(info->mute),
++             *proplist_str ? "\n\t\t" : _("(none)"),
++             proplist_str);
++
++    pa_xfree(proplist_str);
++}
++
++static void volume_api_get_server_info_callback(pa_context *c, const pa_ext_volume_api_server_info *info, void *userdata) {
++    pa_assert(c);
++
++    if (!info) {
++        pa_log(_("Failed to get server information: %s"), pa_strerror(pa_context_errno(c)));
++        quit(1);
++        return;
++    }
++
++    main_output_volume_control = info->main_output_volume_control;
++    main_input_volume_control = info->main_input_volume_control;
++    main_output_mute_control = info->main_output_mute_control;
++    main_input_mute_control = info->main_input_mute_control;
++
++    if (main_output_volume_control == PA_INVALID_INDEX)
++        printf(_("Main output volume control: (unset)\n"));
++
++    if (main_input_volume_control == PA_INVALID_INDEX)
++        printf(_("Main input volume control: (unset)\n"));
++
++    if (main_output_mute_control == PA_INVALID_INDEX)
++        printf(_("Main output mute control: (unset)\n"));
++
++    if (main_input_mute_control == PA_INVALID_INDEX)
++        printf(_("Main input mute control: (unset)\n"));
++
++    if (main_output_volume_control != PA_INVALID_INDEX || main_input_volume_control != PA_INVALID_INDEX) {
++        pa_operation_unref(pa_ext_volume_api_get_volume_control_info_list(c, get_volume_control_info_callback, NULL));
++        actions++;
++    }
++
++    if (main_output_mute_control != PA_INVALID_INDEX || main_input_mute_control != PA_INVALID_INDEX) {
++        pa_operation_unref(pa_ext_volume_api_get_mute_control_info_list(c, get_mute_control_info_callback, NULL));
++        actions++;
++    }
++
++    complete_action();
++}
++
++static void get_device_info_callback(pa_context *c, const pa_ext_volume_api_device_info *info, int is_last,
++                                     void *userdata) {
++    char *device_types_str = NULL;
++    char *volume_control_str;
++    char *mute_control_str;
++    char *proplist_str;
++
++    pa_assert(c);
++
++    if (is_last < 0) {
++        pa_log(_("Failed to get device information: %s"), pa_strerror(pa_context_errno(c)));
++        quit(1);
++        return;
++    }
++
++    if (is_last) {
++        complete_action();
++        return;
++    }
++
++    pa_assert(info);
++
++    if (nl && !short_list_format)
++        printf("\n");
++    nl = true;
++
++    if (info->n_device_types > 0)
++        device_types_str = pa_join(info->device_types, info->n_device_types, ", ");
++    else
++        device_types_str = pa_xstrdup(_("(none)"));
++
++    if (info->volume_control != PA_INVALID_INDEX)
++        volume_control_str = pa_sprintf_malloc("%u", info->volume_control);
++    else
++        volume_control_str = pa_xstrdup(_("(unset)"));
++
++    if (info->mute_control != PA_INVALID_INDEX)
++        mute_control_str = pa_sprintf_malloc("%u", info->mute_control);
++    else
++        mute_control_str = pa_xstrdup(_("(unset)"));
++
++    if (short_list_format) {
++        printf("%u\t%s\t%s\t%s\t%s\t%s\n", info->index, info->name, pa_direction_to_string(info->direction), device_types_str,
++               volume_control_str, mute_control_str);
++        pa_xfree(mute_control_str);
++        pa_xfree(volume_control_str);
++        pa_xfree(device_types_str);
++        return;
++    }
++
++    proplist_str = pa_proplist_to_string_sep(info->proplist, "\n\t\t");
++
++    printf(_("Device #%u\n"
++             "\tName: %s\n"
++             "\tDescription: %s\n"
++             "\tDirection: %s\n"
++             "\tDevice Types: %s\n"
++             "\tVolume Control: %s\n"
++             "\tMute Control: %s\n"
++             "\tProperties: %s%s\n"),
++             info->index,
++             info->name,
++             info->description,
++             pa_direction_to_string(info->direction),
++             device_types_str,
++             volume_control_str,
++             mute_control_str,
++             *proplist_str ? "\n\t\t" : _("(none)"),
++             proplist_str);
++
++    pa_xfree(proplist_str);
++    pa_xfree(mute_control_str);
++    pa_xfree(volume_control_str);
++    pa_xfree(device_types_str);
++}
++
++static void get_stream_info_callback(pa_context *c, const pa_ext_volume_api_stream_info *info, int is_last,
++                                     void *userdata) {
++    char *volume_control_str;
++    char *mute_control_str;
++    char *proplist_str;
++
++    pa_assert(c);
++
++    if (is_last < 0) {
++        pa_log(_("Failed to get stream information: %s"), pa_strerror(pa_context_errno(c)));
++        quit(1);
++        return;
++    }
++
++    if (is_last) {
++        complete_action();
++        return;
++    }
++
++    pa_assert(info);
++
++    if (nl && !short_list_format)
++        printf("\n");
++    nl = true;
++
++    if (info->volume_control != PA_INVALID_INDEX)
++        volume_control_str = pa_sprintf_malloc("%u", info->volume_control);
++    else
++        volume_control_str = pa_xstrdup(_("(unset)"));
++
++    if (info->mute_control != PA_INVALID_INDEX)
++        mute_control_str = pa_sprintf_malloc("%u", info->mute_control);
++    else
++        mute_control_str = pa_xstrdup(_("(unset)"));
++
++    if (short_list_format) {
++        printf("%u\t%s\t%s\t%s\t%s\n", info->index, info->name, pa_direction_to_string(info->direction), volume_control_str,
++               mute_control_str);
++        pa_xfree(mute_control_str);
++        pa_xfree(volume_control_str);
++        return;
++    }
++
++    proplist_str = pa_proplist_to_string_sep(info->proplist, "\n\t\t");
++
++    printf(_("Stream #%u\n"
++             "\tName: %s\n"
++             "\tDescription: %s\n"
++             "\tDirection: %s\n"
++             "\tVolume Control: %s\n"
++             "\tMute Control: %s\n"
++             "\tProperties: %s%s\n"),
++             info->index,
++             info->name,
++             info->description,
++             pa_direction_to_string(info->direction),
++             volume_control_str,
++             mute_control_str,
++             *proplist_str ? "\n\t\t" : _("(none)"),
++             proplist_str);
++
++    pa_xfree(proplist_str);
++    pa_xfree(mute_control_str);
++    pa_xfree(volume_control_str);
++}
++
++static void get_audio_group_info_callback(pa_context *c, const pa_ext_volume_api_audio_group_info *info, int is_last,
++                                          void *userdata) {
++    char *volume_control_str;
++    char *mute_control_str;
++    char *proplist_str;
++
++    pa_assert(c);
++
++    if (is_last < 0) {
++        pa_log(_("Failed to get audio group information: %s"), pa_strerror(pa_context_errno(c)));
++        quit(1);
++        return;
++    }
++
++    if (is_last) {
++        complete_action();
++        return;
++    }
++
++    pa_assert(info);
++
++    if (nl && !short_list_format)
++        printf("\n");
++    nl = true;
++
++    if (info->volume_control != PA_INVALID_INDEX)
++        volume_control_str = pa_sprintf_malloc("%u", info->volume_control);
++    else
++        volume_control_str = pa_xstrdup(_("(unset)"));
++
++    if (info->mute_control != PA_INVALID_INDEX)
++        mute_control_str = pa_sprintf_malloc("%u", info->mute_control);
++    else
++        mute_control_str = pa_xstrdup(_("(unset)"));
++
++    if (short_list_format) {
++        printf("%u\t%s\t%s\t%s\n", info->index, info->name, volume_control_str, mute_control_str);
++        pa_xfree(mute_control_str);
++        pa_xfree(volume_control_str);
++        return;
++    }
++
++    proplist_str = pa_proplist_to_string_sep(info->proplist, "\n\t\t");
++
++    printf(_("Audio Group #%u\n"
++             "\tName: %s\n"
++             "\tDescription: %s\n"
++             "\tVolume Control: %s\n"
++             "\tMute Control: %s\n"
++             "\tProperties: %s%s\n"),
++             info->index,
++             info->name,
++             info->description,
++             volume_control_str,
++             mute_control_str,
++             *proplist_str ? "\n\t\t" : _("(none)"),
++             proplist_str);
++
++    pa_xfree(proplist_str);
++    pa_xfree(mute_control_str);
++    pa_xfree(volume_control_str);
++}
++
++static const char *volume_api_subscription_event_facility_to_string(pa_ext_volume_api_subscription_event_type_t type) {
++
++    switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
++        case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_SERVER:
++            return _("server (volume API)");
++
++        case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_VOLUME_CONTROL:
++            return _("volume-control");
++
++        case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_MUTE_CONTROL:
++            return _("mute-control");
++
++        case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_DEVICE:
++            return _("device");
++
++        case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_STREAM:
++            return _("stream");
++
++        case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_AUDIO_GROUP:
++            return _("audio-group");
++    }
++
++    return _("unknown");
++}
++
++static void volume_api_subscribe_cb(pa_context *c, pa_ext_volume_api_subscription_event_type_t event_type, uint32_t idx,
++                                    void *userdata) {
++    pa_assert(c);
++
++    printf(_("Event '%s' on %s #%u\n"),
++           subscription_event_type_to_string(event_type),
++           volume_api_subscription_event_facility_to_string(event_type),
++           idx);
++    fflush(stdout);
++}
++
++static void volume_api_state_cb(pa_context *c, void *userdata) {
++    pa_ext_volume_api_state_t state;
++
++    pa_assert(c);
++
++    state = pa_ext_volume_api_get_state(c);
++
++    switch (state) {
++        case PA_EXT_VOLUME_API_STATE_READY: {
++            pa_operation *o = NULL;
++
++            volume_api_connected = true;
++
++            switch (action) {
++                case INFO:
++                    o = pa_ext_volume_api_get_server_info(c, volume_api_get_server_info_callback, NULL);
++                    actions++;
++                    break;
++
++                case LIST:
++                    if (!list_type) {
++                        o = pa_ext_volume_api_get_volume_control_info_list(c, get_volume_control_info_callback, NULL);
++                        pa_operation_unref(o);
++                        o = pa_ext_volume_api_get_mute_control_info_list(c, get_mute_control_info_callback, NULL);
++                        pa_operation_unref(o);
++                        o = pa_ext_volume_api_get_device_info_list(c, get_device_info_callback, NULL);
++                        pa_operation_unref(o);
++                        o = pa_ext_volume_api_get_stream_info_list(c, get_stream_info_callback, NULL);
++                        pa_operation_unref(o);
++                        o = pa_ext_volume_api_get_audio_group_info_list(c, get_audio_group_info_callback, NULL);
++                        pa_operation_unref(o);
++                        o = NULL;
++                        actions += 4;
++                    } else if (pa_streq(list_type, "volume-controls")) {
++                        o = pa_ext_volume_api_get_volume_control_info_list(c, get_volume_control_info_callback, NULL);
++                        actions++;
++                    } else if (pa_streq(list_type, "mute-controls")) {
++                        o = pa_ext_volume_api_get_mute_control_info_list(c, get_mute_control_info_callback, NULL);
++                        actions++;
++                    } else if (pa_streq(list_type, "devices")) {
++                        o = pa_ext_volume_api_get_device_info_list(c, get_device_info_callback, NULL);
++                        actions++;
++                    } else if (pa_streq(list_type, "streams")) {
++                        o = pa_ext_volume_api_get_stream_info_list(c, get_stream_info_callback, NULL);
++                        actions++;
++                    } else if (pa_streq(list_type, "audio-groups")) {
++                        o = pa_ext_volume_api_get_audio_group_info_list(c, get_audio_group_info_callback, NULL);
++                        actions++;
++                    }
++                    break;
++
++                case SET_VOLUME_CONTROL_VOLUME:
++                    if (!balance_valid && !(volume_flags & VOL_RELATIVE)) {
++                        pa_assert(volume_valid);
++                        o = pa_ext_volume_api_set_volume_control_volume_by_name(c, volume_control_name, &bvolume, true,
++                                                                                false, simple_callback, NULL);
++                    } else
++                        o = pa_ext_volume_api_get_volume_control_info_by_name(c, volume_control_name,
++                                                                              get_volume_control_info_callback, NULL);
++
++                    actions++;
++                    break;
++
++                case SET_MUTE_CONTROL_MUTE:
++                    if (mute == TOGGLE_MUTE)
++                        o = pa_ext_volume_api_get_mute_control_info_by_name(c, mute_control_name,
++                                                                                  get_mute_control_info_callback, NULL);
++                    else
++                        o = pa_ext_volume_api_set_mute_control_mute_by_name(c, mute_control_name, mute, simple_callback,
++                                                                            NULL);
++
++                    actions++;
++                    break;
++
++                case SUBSCRIBE:
++                    pa_ext_volume_api_set_subscribe_callback(c, volume_api_subscribe_cb, NULL);
++                    o = pa_ext_volume_api_subscribe(c, PA_EXT_VOLUME_API_SUBSCRIPTION_MASK_ALL, NULL, NULL);
++                    break;
++
++                default:
++                    break;
++            }
++
++            if (o)
++                pa_operation_unref(o);
++
++            complete_action();
++            break;
++        }
++
++        case PA_EXT_VOLUME_API_STATE_FAILED:
++            pa_log("Volume API context failed: %s", pa_strerror(pa_context_errno(c)));
++
++            /* If the main context failed too, let's not do anything, because
++             * calling complete_action() would reset the context error code to
++             * PA_ERR_BADSTATE, meaning that the original error code would be
++             * lost. */
++            if (pa_context_get_state(c) == PA_CONTEXT_FAILED)
++                break;
++
++            if (action == INFO || (action == LIST && !list_type) || action == SUBSCRIBE) {
++                /* In these cases we shouldn't exit with an error if the volume
++                 * API happens to be or become unavailable. If we haven't yet
++                 * connected to the volume API, then we need to complete the
++                 * "connect to volume API" action. */
++
++                if (!volume_api_connected)
++                    complete_action();
++            } else
++                quit(1);
++
++            break;
++
++        default:
++            break;
++    }
++}
++
++static void connect_to_volume_api(void) {
++    int r;
++
++    pa_assert(context);
++
++    pa_ext_volume_api_set_state_callback(context, volume_api_state_cb, NULL);
++
++    r = pa_ext_volume_api_connect(context);
++    if (r >= 0)
++        actions++;
++}
++
+ static void context_state_callback(pa_context *c, void *userdata) {
+     pa_operation *o = NULL;
+@@ -1215,6 +1797,7 @@ static void context_state_callback(pa_context *c, void *userdata) {
+                 case INFO:
+                     o = pa_context_get_server_info(c, get_server_info_callback, NULL);
++                    connect_to_volume_api();
+                     break;
+                 case PLAY_SAMPLE:
+@@ -1259,7 +1842,14 @@ static void context_state_callback(pa_context *c, void *userdata) {
+                             o = pa_context_get_card_info_list(c, get_card_info_callback, NULL);
+                       else if (pa_streq(list_type, "nodes"))
+                           o = pa_ext_node_manager_read_nodes(c, node_list_callback, NULL);
+-                        else
++                        else if (pa_streq(list_type, "volume-controls")
++                                     || pa_streq(list_type, "mute-controls")
++                                     || pa_streq(list_type, "devices")
++                                     || pa_streq(list_type, "streams")
++                                     || pa_streq(list_type, "audio-groups")) {
++                            connect_to_volume_api();
++                            o = NULL;
++                        } else
+                             pa_assert_not_reached();
+                     } else {
+                         o = pa_context_get_module_info_list(c, get_module_info_callback, NULL);
+@@ -1309,6 +1899,7 @@ static void context_state_callback(pa_context *c, void *userdata) {
+                             actions++;
+                         }
++                        connect_to_volume_api();
+                         o = NULL;
+                     }
+                     break;
+@@ -1442,6 +2033,11 @@ static void context_state_callback(pa_context *c, void *userdata) {
+                     o = pa_context_set_port_latency_offset(c, card_name, port_name, latency_offset, simple_callback, NULL);
+                     break;
++                case SET_VOLUME_CONTROL_VOLUME:
++                case SET_MUTE_CONTROL_MUTE:
++                    connect_to_volume_api();
++                    break;
++
+                 case SUBSCRIBE:
+                     pa_context_set_subscribe_callback(c, context_subscribe_callback, NULL);
+@@ -1457,6 +2053,14 @@ static void context_state_callback(pa_context *c, void *userdata) {
+                                              PA_SUBSCRIPTION_MASK_CARD,
+                                              NULL,
+                                              NULL);
++
++                    if (o) {
++                        pa_operation_unref(o);
++                        actions++;
++                        o = NULL;
++                    }
++
++                    connect_to_volume_api();
+                     break;
+               case NODE_CONNECT:
+                   pa_operation_unref(pa_ext_node_manager_connect_nodes(c,
+@@ -1599,6 +2203,9 @@ static void help(const char *argv0) {
+     printf("%s %s %s %s\n", argv0, _("[options]"), "set-(sink-input|source-output)-mute", _("#N 1|0|toggle"));
+     printf("%s %s %s %s\n", argv0, _("[options]"), "set-sink-formats", _("#N FORMATS"));
+     printf("%s %s %s %s\n", argv0, _("[options]"), "set-port-latency-offset", _("CARD-NAME|CARD-#N PORT OFFSET"));
++    printf("%s %s %s %s\n", argv0, _("[options]"), "set-volume-control-volume", _("NAME|#N VOLUME [BALANCE ...]"));
++    printf("%s %s %s %s\n", argv0, _("[options]"), "set-volume-control-balance", _("NAME|#N BALANCE ..."));
++    printf("%s %s %s %s\n", argv0, _("[options]"), "set-mute-control-mute", _("NAME|#N 1|0|toggle"));
+     printf("%s %s %s\n",    argv0, _("[options]"), "subscribe");
+     printf(_("\nThe special names @DEFAULT_SINK@, @DEFAULT_SOURCE@ and @DEFAULT_MONITOR@\n"
+              "can be used to specify the default sink, source and monitor.\n"));
+@@ -1613,6 +2220,40 @@ static void help(const char *argv0) {
+              "  -n, --client-name=NAME                How to call this client on the server\n"));
+ }
++static int parse_balance(char *argv[], unsigned first_arg, unsigned n_channels, pa_ext_volume_api_bvolume *bv) {
++    pa_ext_volume_api_bvolume bv_local;
++    unsigned i;
++
++    pa_assert(n_channels > 0);
++    pa_assert(bv);
++
++    if (n_channels > PA_CHANNELS_MAX) {
++        pa_log("Too many channels, the maximum is %u.", PA_CHANNELS_MAX);
++        return -1;
++    }
++
++    bv_local = *bv;
++
++    for (i = 0; i < n_channels; i++) {
++        const char *balance_str;
++        double balance;
++
++        balance_str = argv[first_arg + i];
++
++        if (pa_atod(balance_str, &balance) < 0 || !pa_ext_volume_api_balance_valid(balance)) {
++            pa_log(_("Invalid balance value: %s"), balance_str);
++            return -1;
++        }
++
++        bv_local.balance[i] = balance;
++    }
++
++    bv_local.channel_map.channels = n_channels;
++    *bv = bv_local;
++
++    return 0;
++}
++
+ enum {
+     ARG_VERSION = 256
+ };
+@@ -1698,15 +2339,19 @@ int main(int argc, char *argv[]) {
+             action = LIST;
+             for (int i = optind+1; i < argc; i++) {
+-                if (pa_streq(argv[i], "modules") || pa_streq(argv[i], "clients") ||
+-                    pa_streq(argv[i], "sinks")   || pa_streq(argv[i], "sink-inputs") ||
+-                    pa_streq(argv[i], "sources") || pa_streq(argv[i], "source-outputs") ||
+-                    pa_streq(argv[i], "samples") || pa_streq(argv[i], "cards") || pa_streq(argv[i], "nodes")) {
++                if (pa_streq(argv[i], "modules")         || pa_streq(argv[i], "clients") ||
++                    pa_streq(argv[i], "sinks")           || pa_streq(argv[i], "sink-inputs") ||
++                    pa_streq(argv[i], "sources")         || pa_streq(argv[i], "source-outputs") ||
++                    pa_streq(argv[i], "samples")         || pa_streq(argv[i], "cards") || pa_streq(argv[i], "nodes") ||
++                    pa_streq(argv[i], "volume-controls") || pa_streq(argv[i], "mute-controls") ||
++                    pa_streq(argv[i], "devices")         || pa_streq(argv[i], "streams") ||
++                    pa_streq(argv[i], "audio-groups")) {
+                     list_type = pa_xstrdup(argv[i]);
+                 } else if (pa_streq(argv[i], "short")) {
+                     short_list_format = true;
+                 } else {
+-                    pa_log(_("Specify nothing, or one of: %s"), "modules, sinks, sources, sink-inputs, source-outputs, clients, samples, cards");
++                    pa_log(_("Specify nothing, or one of: %s"), "modules, sinks, sources, sink-inputs, source-outputs, "
++                             "clients, samples, cards, volume-controls, mute-controls, devices, streams, audio-groups");
+                     goto quit;
+                 }
+             }
+@@ -2092,6 +2737,58 @@ int main(int argc, char *argv[]) {
+           conn_id = (uint32_t) atoi(argv[optind+1]);
++        } else if (pa_streq(argv[optind], "set-volume-control-volume")) {
++            action = SET_VOLUME_CONTROL_VOLUME;
++
++            if (argc < optind + 3) {
++                pa_log(_("You have to specify a volume control name/index and a volume, and optionally balance parameters."));
++                goto quit;
++            }
++
++            volume_control_name = pa_xstrdup(argv[optind + 1]);
++
++            if (parse_volume(argv[optind + 2], &bvolume.volume, &volume_flags) < 0)
++                goto quit;
++
++            volume_valid = true;
++
++            if (argc > optind + 3) {
++                if (parse_balance(argv, optind + 3, argc - (optind + 3), &bvolume) < 0)
++                    goto quit;
++
++                balance_valid = true;
++            }
++
++        } else if (pa_streq(argv[optind], "set-volume-control-balance")) {
++            action = SET_VOLUME_CONTROL_VOLUME;
++
++            if (argc < optind + 3) {
++                pa_log(_("You have to specify a volume control name/index and balance parameters."));
++                goto quit;
++            }
++
++            volume_control_name = pa_xstrdup(argv[optind + 1]);
++
++            if (parse_balance(argv, optind + 2, argc - (optind + 2), &bvolume) < 0)
++                goto quit;
++
++            balance_valid = true;
++
++        } else if (pa_streq(argv[optind], "set-mute-control-mute")) {
++            action = SET_MUTE_CONTROL_MUTE;
++
++            if (argc != optind + 3) {
++                pa_log(_("You have to specify a mute control name/index and a mute value."));
++                goto quit;
++            }
++
++            mute_control_name = pa_xstrdup(argv[optind + 1]);
++
++            if ((mute = parse_mute(argv[optind + 2])) == INVALID_MUTE) {
++                pa_log(_("Invalid mute specification"));
++                goto quit;
++            }
++
+       } else if (pa_streq(argv[optind], "help")) {
+             help(bn);
+             ret = 0;
+@@ -2154,6 +2851,8 @@ quit:
+     pa_xfree(profile_name);
+     pa_xfree(port_name);
+     pa_xfree(formats);
++    pa_xfree(mute_control_name);
++    pa_xfree(volume_control_name);
+     if (sndfile)
+         sf_close(sndfile);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0081-Add-module-audio-groups.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0081-Add-module-audio-groups.patch
new file mode 100644 (file)
index 0000000..b918bff
--- /dev/null
@@ -0,0 +1,1425 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Wed, 21 May 2014 14:08:40 +0300
+Subject: Add module-audio-groups
+
+Change-Id: Iaa0284e0537785ed245caae6e49544477ca246b9
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/Makefile.am                                    |   13 +-
+ src/daemon/default.pa.in                           |    4 +
+ src/modules/audio-groups/audio-groups.conf.example |   28 +
+ src/modules/audio-groups/module-audio-groups.c     | 1317 ++++++++++++++++++++
+ 4 files changed, 1360 insertions(+), 2 deletions(-)
+ create mode 100644 src/modules/audio-groups/audio-groups.conf.example
+ create mode 100644 src/modules/audio-groups/module-audio-groups.c
+
+diff --git a/src/Makefile.am b/src/Makefile.am
+index e075c1d..a6bb319 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -1082,6 +1082,12 @@ libvolume_api_la_SOURCES = \
+ libvolume_api_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version
+ libvolume_api_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la
++# Audio Groups
++module_audio_groups_la_SOURCES = modules/audio-groups/module-audio-groups.c
++module_audio_groups_la_LDFLAGS = $(MODULE_LDFLAGS)
++module_audio_groups_la_LIBADD = $(MODULE_LIBADD) libvolume-api.la
++module_audio_groups_la_CFLAGS = $(AM_CFLAGS)
++
+ 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
+@@ -1168,7 +1174,8 @@ modlibexec_LTLIBRARIES += \
+               module-switch-on-port-available.la \
+               module-filter-apply.la \
+               module-filter-heuristics.la \
+-              module-role-ducking.la
++              module-role-ducking.la \
++              module-audio-groups.la
+ if HAVE_ESOUND
+ modlibexec_LTLIBRARIES += \
+@@ -1505,7 +1512,9 @@ SYMDEF_FILES = \
+               module-switch-on-connect-symdef.h \
+               module-switch-on-port-available-symdef.h \
+               module-filter-apply-symdef.h \
+-              module-filter-heuristics-symdef.h
++              module-filter-heuristics-symdef.h \
++              module-audio-groups-symdef.h
++
+ if USE_SAMSUNG_POLICY
+ SYMDEF_FILES += \
+               module-policy-symdef.h
+diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in
+index a21ae04..7cf52a4 100755
+--- a/src/daemon/default.pa.in
++++ b/src/daemon/default.pa.in
+@@ -193,6 +193,10 @@ ifelse(@HAVE_X11@, 1, [dnl
+ load-module module-volume-api
+ .endif
++.ifexists module-audio-groups
++load-module module-audio-groups
++.endif
++
+ ### Make some devices default
+ #set-default-sink output
+ #set-default-source input
+diff --git a/src/modules/audio-groups/audio-groups.conf.example b/src/modules/audio-groups/audio-groups.conf.example
+new file mode 100644
+index 0000000..8acdb76
+--- /dev/null
++++ b/src/modules/audio-groups/audio-groups.conf.example
+@@ -0,0 +1,28 @@
++[General]
++audio-groups = x-example-call-downlink-audio-group x-example-default-output-audio-group x-example-music-output-audio-group
++streams = phone-output music-output default-output
++
++[AudioGroup x-example-call-downlink-audio-group]
++volume-control = create
++mute-control = none
++
++[AudioGroup x-example-default-output-audio-group]
++volume-control = create
++mute-control = none
++
++[AudioGroup x-example-music-output-audio-group]
++volume-control = bind:AudioGroup:x-example-default-output-audio-group
++
++[Stream phone-output]
++match = (direction output AND property media.role=phone)
++audio-group-for-volume = x-example-call-downlink-audio-group
++audio-group-for-mute = x-example-call-downlink-audio-group
++
++[Stream music-output]
++match = (direction output AND property media.role=music)
++audio-group-for-volume = x-example-music-output-audio-group
++audio-group-for-mute = x-example-music-output-audio-group
++
++[Stream default-output]
++audio-group-for-volume = x-example-default-output-audio-group
++audio-group-for-mute = x-example-default-output-audio-group
+diff --git a/src/modules/audio-groups/module-audio-groups.c b/src/modules/audio-groups/module-audio-groups.c
+new file mode 100644
+index 0000000..320847c
+--- /dev/null
++++ b/src/modules/audio-groups/module-audio-groups.c
+@@ -0,0 +1,1317 @@
++/***
++  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 <string.h>
++
++#include <pulse/xmalloc.h>
++
++#include <pulsecore/core-util.h>
++#include <pulsecore/dynarray.h>
++#include <pulsecore/module.h>
++#include <pulsecore/modargs.h>
++#include <pulsecore/conf-parser.h>
++#include <pulsecore/hashmap.h>
++
++#include <modules/volume-api/sstream.h>
++#include <modules/volume-api/volume-control.h>
++#include <modules/volume-api/audio-group.h>
++
++#include "module-audio-groups-symdef.h"
++
++PA_MODULE_AUTHOR("Ismo Puustinen");
++PA_MODULE_DESCRIPTION("Create audio groups and classify streams to them");
++PA_MODULE_VERSION(PACKAGE_VERSION);
++PA_MODULE_LOAD_ONCE(true);
++
++#ifndef AUDIO_GROUP_CONFIG
++#define AUDIO_GROUP_CONFIG "audio-groups.conf"
++#endif
++
++enum match_direction {
++    match_direction_unknown = 0,
++    match_direction_input,
++    match_direction_output,
++};
++
++/* logical expressions */
++
++struct literal {
++
++    /* TODO: this might be parsed to some faster-to-check format? */
++
++    char *property_name;
++    char *property_value;
++    enum match_direction stream_direction;
++
++    bool negation;
++    PA_LLIST_FIELDS(struct literal);
++};
++
++struct conjunction {
++    /* a conjunction of literals */
++    PA_LLIST_HEAD(struct literal, literals);
++    PA_LLIST_FIELDS(struct conjunction);
++};
++
++struct expression {
++    /* this is in disjunctive normal form, so a disjunction of conjunctions */
++    PA_LLIST_HEAD(struct conjunction, conjunctions);
++};
++
++/* data gathered from settings */
++
++enum control_action {
++    CONTROL_ACTION_NONE,
++    CONTROL_ACTION_CREATE,
++    CONTROL_ACTION_BIND,
++};
++
++struct audio_group {
++    struct userdata *userdata;
++    char *id;
++    char *description;
++    enum control_action volume_control_action;
++    enum control_action mute_control_action;
++    pa_binding_target_info *volume_control_target_info;
++    pa_binding_target_info *mute_control_target_info;
++
++    /* official audio group */
++    pa_audio_group *group;
++
++    struct audio_group_control *volume_control;
++    struct audio_group_control *mute_control;
++
++    bool unlinked;
++};
++
++struct stream {
++    struct userdata *userdata;
++    char *id;
++    enum match_direction direction;
++    char *audio_group_name_for_volume;
++    char *audio_group_name_for_mute;
++    pa_audio_group *audio_group_for_volume;
++    pa_audio_group *audio_group_for_mute;
++    pa_binding_target_info *volume_control_target_info;
++    pa_binding_target_info *mute_control_target_info;
++    struct expression *rule;
++
++    bool unlinked;
++};
++
++struct userdata {
++    pa_hashmap *audio_groups; /* name -> struct audio_group */
++    pa_dynarray *streams; /* struct stream */
++    pa_hook_slot *new_stream_volume;
++    pa_hook_slot *new_stream_mute;
++
++    pa_volume_api *api;
++
++    /* The following fields are only used during initialization. */
++    pa_hashmap *audio_group_names; /* name -> name (hashmap-as-a-set) */
++    pa_hashmap *unused_audio_groups; /* name -> struct audio_group */
++    pa_dynarray *stream_names;
++    pa_hashmap *unused_streams; /* name -> struct stream */
++};
++
++static const char* const valid_modargs[] = {
++    "filename",
++    NULL
++};
++
++static void audio_group_unlink(struct audio_group *group);
++
++static void print_literal(struct literal *l);
++static void print_conjunction(struct conjunction *c);
++static void print_expression(struct expression *e);
++static void delete_expression(struct expression *e);
++
++static struct audio_group *audio_group_new(struct userdata *u, const char *name) {
++    struct audio_group *group;
++
++    pa_assert(u);
++    pa_assert(name);
++
++    group = pa_xnew0(struct audio_group, 1);
++    group->userdata = u;
++    group->id = pa_xstrdup(name);
++    group->description = pa_xstrdup(name);
++    group->volume_control_action = CONTROL_ACTION_NONE;
++    group->mute_control_action = CONTROL_ACTION_NONE;
++
++    return group;
++}
++
++static int audio_group_put(struct audio_group *group) {
++    int r;
++
++    pa_assert(group);
++
++    r = pa_audio_group_new(group->userdata->api, group->id, group->description, &group->group);
++    if (r < 0)
++        goto fail;
++
++    switch (group->volume_control_action) {
++        case CONTROL_ACTION_NONE:
++            break;
++
++        case CONTROL_ACTION_CREATE:
++            pa_audio_group_set_have_own_volume_control(group->group, true);
++            pa_audio_group_set_volume_control(group->group, group->group->own_volume_control);
++            break;
++
++        case CONTROL_ACTION_BIND:
++            pa_audio_group_bind_volume_control(group->group, group->volume_control_target_info);
++            break;
++    }
++
++    switch (group->mute_control_action) {
++        case CONTROL_ACTION_NONE:
++            break;
++
++        case CONTROL_ACTION_CREATE:
++            pa_audio_group_set_have_own_mute_control(group->group, true);
++            pa_audio_group_set_mute_control(group->group, group->group->own_mute_control);
++            break;
++
++        case CONTROL_ACTION_BIND:
++            pa_audio_group_bind_mute_control(group->group, group->mute_control_target_info);
++            break;
++    }
++
++    pa_audio_group_put(group->group);
++
++    return 0;
++
++fail:
++    audio_group_unlink(group);
++
++    return r;
++}
++
++static void audio_group_unlink(struct audio_group *group) {
++    pa_assert(group);
++
++    if (group->unlinked)
++        return;
++
++    group->unlinked = true;
++
++    if (group->group) {
++        pa_audio_group_free(group->group);
++        group->group = NULL;
++    }
++}
++
++static void audio_group_free(struct audio_group *group) {
++    pa_assert(group);
++
++    if (!group->unlinked)
++        audio_group_unlink(group);
++
++    if (group->mute_control_target_info)
++        pa_binding_target_info_free(group->mute_control_target_info);
++
++    if (group->volume_control_target_info)
++        pa_binding_target_info_free(group->volume_control_target_info);
++
++    pa_xfree(group->description);
++    pa_xfree(group->id);
++    pa_xfree(group);
++}
++
++static void audio_group_set_description(struct audio_group *group, const char *description) {
++    pa_assert(group);
++    pa_assert(description);
++
++    pa_xfree(group->description);
++    group->description = pa_xstrdup(description);
++}
++
++static void audio_group_set_volume_control_action(struct audio_group *group, enum control_action action,
++                                                  pa_binding_target_info *target_info) {
++    pa_assert(group);
++    pa_assert((action == CONTROL_ACTION_BIND) ^ !target_info);
++
++    group->volume_control_action = action;
++
++    if (group->volume_control_target_info)
++        pa_binding_target_info_free(group->volume_control_target_info);
++
++    if (action == CONTROL_ACTION_BIND)
++        group->volume_control_target_info = pa_binding_target_info_copy(target_info);
++    else
++        group->volume_control_target_info = NULL;
++}
++
++static void audio_group_set_mute_control_action(struct audio_group *group, enum control_action action,
++                                                pa_binding_target_info *target_info) {
++    pa_assert(group);
++    pa_assert((action == CONTROL_ACTION_BIND) ^ !target_info);
++
++    group->mute_control_action = action;
++
++    if (group->mute_control_target_info)
++        pa_binding_target_info_free(group->mute_control_target_info);
++
++    if (action == CONTROL_ACTION_BIND)
++        group->mute_control_target_info = pa_binding_target_info_copy(target_info);
++    else
++        group->mute_control_target_info = NULL;
++}
++
++static struct stream *stream_new(struct userdata *u, const char *name) {
++    struct stream *stream;
++
++    pa_assert(u);
++    pa_assert(name);
++
++    stream = pa_xnew0(struct stream, 1);
++    stream->userdata = u;
++    stream->id = pa_xstrdup(name);
++    stream->direction = match_direction_unknown;
++
++    return stream;
++}
++
++static void stream_put(struct stream *stream) {
++    pa_assert(stream);
++
++    if (stream->audio_group_name_for_volume) {
++        stream->audio_group_for_volume = pa_hashmap_get(stream->userdata->audio_groups, stream->audio_group_name_for_volume);
++        if (stream->audio_group_for_volume)
++            stream->volume_control_target_info =
++                    pa_binding_target_info_new(PA_AUDIO_GROUP_BINDING_TARGET_TYPE, stream->audio_group_for_volume->name,
++                                               PA_AUDIO_GROUP_BINDING_TARGET_FIELD_VOLUME_CONTROL);
++        else
++            pa_log("Stream %s refers to undefined audio group %s.", stream->id, stream->audio_group_name_for_volume);
++    }
++
++    if (stream->audio_group_name_for_mute) {
++        stream->audio_group_for_mute = pa_hashmap_get(stream->userdata->audio_groups, stream->audio_group_name_for_mute);
++        if (stream->audio_group_for_mute)
++            stream->mute_control_target_info =
++                    pa_binding_target_info_new(PA_AUDIO_GROUP_BINDING_TARGET_TYPE, stream->audio_group_for_mute->name,
++                                               PA_AUDIO_GROUP_BINDING_TARGET_FIELD_MUTE_CONTROL);
++        else
++            pa_log("Stream %s refers to undefined audio group %s.", stream->id, stream->audio_group_name_for_volume);
++    }
++}
++
++static void stream_unlink(struct stream *stream) {
++    pa_assert(stream);
++
++    if (stream->unlinked)
++        return;
++
++    if (stream->mute_control_target_info) {
++        pa_binding_target_info_free(stream->mute_control_target_info);
++        stream->mute_control_target_info = NULL;
++    }
++
++    if (stream->volume_control_target_info) {
++        pa_binding_target_info_free(stream->volume_control_target_info);
++        stream->volume_control_target_info = NULL;
++    }
++
++    stream->unlinked = true;
++}
++
++static void stream_free(struct stream *stream) {
++    pa_assert(stream);
++
++    if (!stream->unlinked)
++        stream_unlink(stream);
++
++    if (stream->rule)
++        delete_expression(stream->rule);
++
++    pa_xfree(stream->audio_group_name_for_mute);
++    pa_xfree(stream->audio_group_name_for_volume);
++    pa_xfree(stream->id);
++    pa_xfree(stream);
++}
++
++static void stream_set_audio_group_name_for_volume(struct stream *stream, const char *name) {
++    pa_assert(stream);
++
++    pa_xfree(stream->audio_group_name_for_volume);
++    stream->audio_group_name_for_volume = pa_xstrdup(name);
++}
++
++static void stream_set_audio_group_name_for_mute(struct stream *stream, const char *name) {
++    pa_assert(stream);
++
++    pa_xfree(stream->audio_group_name_for_mute);
++    stream->audio_group_name_for_mute = pa_xstrdup(name);
++}
++
++/* stream classification */
++
++static bool match_predicate(struct literal *l, pas_stream *d) {
++
++    if (l->stream_direction != match_direction_unknown) {
++        /* check the stream direction; _sink inputs_ are always _outputs_ */
++
++        if ((d->direction == PA_DIRECTION_OUTPUT && l->stream_direction == match_direction_output) ||
++            ((d->direction == PA_DIRECTION_INPUT && l->stream_direction == match_direction_input))) {
++            return true;
++        }
++    }
++    else if (l->property_name && l->property_value) {
++        /* check the property from the property list */
++
++        if (pa_proplist_contains(d->proplist, l->property_name) &&
++                strcmp(pa_proplist_gets(d->proplist, l->property_name), l->property_value) == 0)
++            return true;
++    }
++
++    /* no match */
++    return false;
++}
++
++static bool match_rule(struct expression *e, pas_stream *d) {
++
++    struct conjunction *c;
++
++    PA_LLIST_FOREACH(c, e->conjunctions) {
++        struct literal *l;
++        bool and_success = true;
++        PA_LLIST_FOREACH(l, c->literals) {
++            if (!match_predicate(l, d)) {
++                /* at least one fail for conjunction */
++                and_success = false;
++                break;
++            }
++        }
++
++        if (and_success) {
++            /* at least one match for disjunction */
++            return true;
++        }
++    }
++
++    /* no matches */
++    return false;
++}
++
++static void classify_stream(struct userdata *u, pas_stream *new_data, bool mute) {
++    /* do the classification here */
++
++    struct stream *stream = NULL;
++    unsigned idx;
++
++    /* go through the stream match definitions in given order */
++
++    PA_DYNARRAY_FOREACH(stream, u->streams, idx) {
++        if (stream->rule && match_rule(stream->rule, new_data)) {
++            pa_log_info("stream %s (%s) match with rule %s:", new_data->name, new_data->description, stream->id);
++            print_expression(stream->rule);
++
++            if (mute) {
++                if (new_data->use_default_mute_control && stream->audio_group_for_mute)
++                    pas_stream_bind_mute_control(new_data, stream->mute_control_target_info);
++            } else {
++                if (new_data->use_default_volume_control && stream->audio_group_for_volume)
++                    pas_stream_bind_volume_control(new_data, stream->volume_control_target_info);
++            }
++
++            return;
++        }
++    }
++
++    /* no matches, don't touch the volumes */
++}
++
++static pa_hook_result_t set_volume_control_cb(
++        void *hook_data,
++        pas_stream *new_data,
++        struct userdata *u) {
++
++    pa_assert(new_data);
++    pa_assert(u);
++
++    classify_stream(u, new_data, false);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t set_mute_control_cb(
++        void *hook_data,
++        pas_stream *new_data,
++        struct userdata *u) {
++
++    pa_assert(new_data);
++    pa_assert(u);
++
++    classify_stream(u, new_data, true);
++
++    return PA_HOOK_OK;
++}
++
++/* parser for configuration file */
++
++/*
++    Parse the match expression. The syntax is this:
++
++    OPER           := "AND" | "OR"
++    OPEN_BRACE     := "("
++    CLOSE_BRACE    := ")"
++    EXPR           := OPEN_BRACE EXPR OPER EXPR CLOSE_BRACE | VAR
++    VAR            := LIT | "NEG" LIT
++    LIT            := PREDICATE (defined by rule semantics)
++
++    In addition there is a requirement that the expressions need to be in
++    disjunctive normal form. It means that if there is an expression that
++    has AND operator, there may not be any OR operators in its subexpressions.
++
++    Example expressions:
++
++    (foo)
++    (foo AND bar)
++    (foo OR (bar AND xxx))
++    (NEG foo OR (bar AND NEG xxx))
++
++    The predicate here is the single rule that is matched against the new sink
++    input. The syntax is this:
++
++    PREDICATE      := "direction" DIRECTION  | "property" PROPERTY
++    DIRECTION      := "input" | "output"
++    PROPERTY       := PROPERTY_NAME "=" PROPERTY_VALUE
++    PROPERTY_NAME  := STRING
++    PROPERTY_VALUE := STRING
++
++    The allowed characters for STRING are standard ascii characters. Not
++    allowed substrings are the reserved words "AND", "OR", "(", ")", "NEG" and
++    "=".
++
++    Complete examples:
++
++    (property application.process.binary=paplay)
++
++    (property media.role=music AND direction input)
++
++    (property application.process.binary=paplay OR (direction input OR direction output))
++*/
++
++static void print_literal(struct literal *l) {
++    if (l->stream_direction != match_direction_unknown) {
++        pa_log_info("       %sstream direction %s",
++                l->negation ? "NEG " : "",
++                l->stream_direction == match_direction_input ? "input" : "output");
++    }
++    else {
++        pa_log_info("       %sproperty %s == %s",
++                l->negation ? "NEG " : "",
++                l->property_name ? l->property_name : "NULL",
++                l->property_value ? l->property_value : "NULL");
++    }
++}
++
++static void print_conjunction(struct conjunction *c) {
++    struct literal *l;
++    pa_log_info("   conjunction for literals:");
++    PA_LLIST_FOREACH(l, c->literals) {
++        print_literal(l);
++    }
++}
++
++static void print_expression(struct expression *e) {
++    struct conjunction *c;
++    pa_log_info("disjunction for conjunctions:");
++    PA_LLIST_FOREACH(c, e->conjunctions) {
++        print_conjunction(c);
++    }
++}
++
++static void delete_literal(struct literal *l) {
++
++    if (!l)
++        return;
++
++    pa_xfree(l->property_name);
++    pa_xfree(l->property_value);
++    pa_xfree(l);
++}
++
++static void delete_conjunction(struct conjunction *c) {
++    struct literal *l;
++
++    if (!c)
++        return;
++
++    PA_LLIST_FOREACH(l, c->literals) {
++        delete_literal(l);
++    }
++
++    pa_xfree(c);
++}
++
++static void delete_expression(struct expression *e) {
++    struct conjunction *c;
++
++    PA_LLIST_FOREACH(c, e->conjunctions) {
++        delete_conjunction(c);
++    }
++
++    pa_xfree(e);
++}
++
++enum logic_operator {
++    operator_not_set = 0,
++    operator_and,
++    operator_or,
++};
++
++struct expression_token {
++    struct expression_token *left;
++    struct expression_token *right;
++
++    enum logic_operator oper;
++
++    struct literal_token *lit;
++};
++
++struct literal_token {
++    bool negation;
++    char *var;
++};
++
++static void delete_literal_token(struct literal_token *l) {
++
++    if (!l)
++        return;
++
++    pa_xfree(l->var);
++    pa_xfree(l);
++}
++
++static void delete_expression_token(struct expression_token *e) {
++
++    if (!e)
++        return;
++
++    delete_expression_token(e->left);
++    delete_expression_token(e->right);
++    delete_literal_token(e->lit);
++
++    e->left = NULL;
++    e->right = NULL;
++    e->lit = NULL;
++
++    pa_xfree(e);
++}
++
++static struct expression_token *parse_rule_internal(const char *rule, bool disjunction_allowed) {
++
++    int len = strlen(rule);
++    struct expression_token *et;
++    char *p;
++    int brace_count = 0;
++    bool braces_present = false;
++    char left_buf[len];
++    char right_buf[len];
++
++#if 0
++    /* check if the rule is still valid */
++
++    if (len < 2)
++        return NULL;
++
++    if (rule[0] != '(' || rule[len-1] != ')')
++        return NULL;
++#endif
++
++    et = pa_xnew0(struct expression_token, 1);
++
++    if (!et)
++        return NULL;
++
++    /* count the braces -- we want to find the case when there is only one brace open */
++
++    p = (char *) rule;
++
++    while (*p) {
++        if (*p == '(') {
++            braces_present = true;
++            brace_count++;
++        }
++        else if (*p == ')') {
++            brace_count--;
++        }
++
++        if (brace_count == 1) {
++
++            /* the parser is recursive and just goes down the tree on the
++             * topmost level (where the brace count is 1). If there are no
++             * braces this is a literal */
++
++            /* find the operator AND or OR */
++
++            if (strncmp(p, "AND", 3) == 0) {
++
++                /* copy parts */
++                char *begin_left = (char *) rule+1;
++                char *begin_right = p+3;
++
++                int left_len = p - rule - 1; /* minus '(' */
++                int right_len = len - 3 - left_len - 2; /* minus AND and '(' and ')'*/
++
++                memcpy(left_buf, begin_left, left_len);
++                left_buf[left_len] = '\0';
++                memcpy(right_buf, begin_right, right_len);
++                right_buf[right_len] = '\0';
++
++                et->left = parse_rule_internal(left_buf, false);
++                et->right = parse_rule_internal(right_buf, false);
++                et->oper = operator_and;
++
++                if (!et->left || !et->right) {
++                    delete_expression_token(et);
++                    return NULL;
++                }
++
++                return et;
++            }
++            else if (strncmp(p, "OR", 2) == 0) {
++
++                char *begin_left = (char *) rule+1;
++                char *begin_right = p+2;
++
++                int left_len = p - rule - 1; /* minus '(' */
++                int right_len = len - 2 - left_len - 2; /* minus OR and '(' and ')'*/
++
++                if (!disjunction_allowed) {
++                    pa_log_error("logic expression not in dnf");
++                    delete_expression_token(et);
++                    return NULL;
++                }
++
++                memcpy(left_buf, begin_left, left_len);
++                left_buf[left_len] = '\0';
++                memcpy(right_buf, begin_right, right_len);
++                right_buf[right_len] = '\0';
++
++                et->left = parse_rule_internal(left_buf, true);
++                et->right = parse_rule_internal(right_buf, true);
++                et->oper = operator_or;
++
++                if (!et->left || !et->right) {
++                    delete_expression_token(et);
++                    return NULL;
++                }
++
++                return et;
++            }
++            /* else a literal which is inside braces */
++        }
++
++        p++;
++    }
++
++    if (brace_count != 0) {
++        /* the input is not valid */
++        pa_log_error("mismatched braces in logic expression");
++        delete_expression_token(et);
++        return NULL;
++    }
++    else {
++        /* this is a literal */
++        char *begin_lit;
++        char buf[strlen(rule)+1];
++
++        struct literal_token *lit = pa_xnew0(struct literal_token, 1);
++        if (!lit) {
++            delete_expression_token(et);
++            return NULL;
++        }
++
++        if (braces_present) {
++            /* remove all braces */
++            char *k;
++            char *l;
++
++            k = (char *) rule;
++            l = buf;
++
++            while (*k) {
++                if (*k == '(' || *k == ')') {
++                    k++;
++                    continue;
++                }
++
++                *l = *k;
++                l++;
++                k++;
++            }
++            *l = '\0';
++        }
++        else {
++            strncpy(buf, rule, sizeof(buf));
++        }
++
++        if (strncmp(buf, "NEG", 3) == 0) {
++            begin_lit = (char *) buf + 3;
++            lit->negation = true;
++        }
++        else {
++            begin_lit = (char *) buf;
++            lit->negation = false;
++        }
++
++        lit->var = pa_xstrdup(begin_lit);
++        et->lit = lit;
++    }
++
++    return et;
++}
++
++static bool gather_literal(struct expression_token *et, struct literal *l) {
++#define PROPERTY_KEYWORD "property"
++#define DIRECTION_KEYWORD "direction"
++#define DIRECTION_VALUE_INPUT "input"
++#define DIRECTION_VALUE_OUTPUT "output"
++
++    char *p = et->lit->var;
++    int len = strlen(et->lit->var);
++
++    l->negation = et->lit->negation;
++
++    if (strncmp(p, PROPERTY_KEYWORD, strlen(PROPERTY_KEYWORD)) == 0) {
++        char name[len];
++        char value[len];
++        int i = 0;
++
++        p += strlen(PROPERTY_KEYWORD);
++
++        /* parse the property pair: name=value */
++
++        while (*p && *p != '=') {
++            name[i++] = *p;
++            p++;
++        }
++
++        /* check if we really found '=' */
++
++        if (*p != '=') {
++            pa_log_error("property syntax broken for '%s'", et->lit->var);
++            goto error;
++        }
++
++        name[i] = '\0';
++
++        p++;
++        i = 0;
++
++        while (*p) {
++            value[i++] = *p;
++            p++;
++        }
++
++        value[i] = '\0';
++
++        l->property_name = pa_xstrdup(name);
++        l->property_value = pa_xstrdup(value);
++    }
++    else if (strncmp(p, DIRECTION_KEYWORD, strlen(DIRECTION_KEYWORD)) == 0) {
++        p += strlen(DIRECTION_KEYWORD);
++
++        if (strncmp(p, DIRECTION_VALUE_INPUT, strlen(DIRECTION_VALUE_INPUT)) == 0) {
++            l->stream_direction = match_direction_input;
++        }
++        else if (strncmp(p, DIRECTION_VALUE_OUTPUT, strlen(DIRECTION_VALUE_OUTPUT)) == 0) {
++            l->stream_direction = match_direction_output;
++        }
++        else {
++            pa_log_error("unknown direction(%s): %s", et->lit->var, p);
++            goto error;
++        }
++    }
++    else {
++        pa_log_error("not able to parse the value: '%s'", et->lit->var);
++        goto error;
++    }
++
++    return true;
++
++error:
++    return false;
++
++#undef DIRECTION_VALUE_OUTPUT
++#undef DIRECTION_VALUE_INPUT
++#undef DIRECTION_KEYWORD
++#undef PROPERTY_KEYWORD
++}
++
++static bool gather_conjunction(struct expression_token *et, struct conjunction *c) {
++
++    if (et->oper == operator_and) {
++        if (!gather_conjunction(et->left, c) ||
++            !gather_conjunction(et->right, c))
++            return false;
++    }
++    else {
++        /* literal */
++        struct literal *l = pa_xnew0(struct literal, 1);
++
++        if (!l)
++            return false;
++
++        gather_literal(et, l);
++
++        PA_LLIST_PREPEND(struct literal, c->literals, l);
++    }
++
++    return true;
++}
++
++static bool gather_expression(struct expression *e, struct expression_token *et) {
++
++    if (et->oper == operator_or) {
++        if (!gather_expression(e, et->right) ||
++            !gather_expression(e, et->left))
++            return false;
++    }
++    else {
++        /* conjunction or literal */
++        struct conjunction *c = pa_xnew0(struct conjunction, 1);
++        if (!gather_conjunction(et, c))
++            return false;
++
++        PA_LLIST_PREPEND(struct conjunction, e->conjunctions, c);
++    }
++
++    return true;
++}
++
++static struct expression *parse_rule(const char *rule_string) {
++    char *k, *l;
++    struct expression *e = NULL;
++    int len;
++    char *buf = NULL;
++    struct expression_token *et = NULL;
++
++    if (!rule_string)
++        goto error;
++
++    len = strlen(rule_string);
++
++    buf = (char *) pa_xmalloc0(len);
++
++    if (!buf)
++        goto error;
++
++    /* remove whitespace */
++
++    k = (char *) rule_string;
++    l = buf;
++
++    while (*k) {
++        if (*k == ' ') {
++            k++;
++            continue;
++        }
++
++        *l = *k;
++        l++;
++        k++;
++    }
++
++    /* et is the root of an expression tree */
++    et = parse_rule_internal(buf, true);
++
++    if (!et)
++        goto error;
++
++    e = pa_xnew0(struct expression, 1);
++
++    if (!e)
++        goto error;
++
++    /* gather expressions to actual match format */
++    gather_expression(e, et);
++
++#if 1
++    print_expression(e);
++#endif
++
++    /* free memory */
++    delete_expression_token(et);
++    pa_xfree(buf);
++
++    return e;
++
++error:
++    delete_expression_token(et);
++    pa_xfree(buf);
++    pa_xfree(e);
++    return NULL;
++}
++
++static int parse_audio_groups(pa_config_parser_state *state) {
++    struct userdata *u;
++    char *name;
++    const char *split_state = NULL;
++
++    pa_assert(state);
++
++    u = state->userdata;
++
++    pa_hashmap_remove_all(u->audio_group_names);
++
++    while ((name = pa_split_spaces(state->rvalue, &split_state)))
++        pa_hashmap_put(u->audio_group_names, name, name);
++
++    return 0;
++}
++
++static int parse_streams(pa_config_parser_state *state) {
++    struct userdata *u;
++    char *name;
++    const char *split_state = NULL;
++
++    pa_assert(state);
++
++    u = state->userdata;
++
++    pa_dynarray_remove_all(u->stream_names);
++
++    while ((name = pa_split_spaces(state->rvalue, &split_state))) {
++        const char *name2;
++        unsigned idx;
++        bool duplicate = false;
++
++        /* Avoid adding duplicates in u->stream_names. */
++        PA_DYNARRAY_FOREACH(name2, u->stream_names, idx) {
++            if (pa_streq(name, name2)) {
++                duplicate = true;
++                break;
++            }
++        }
++
++        if (duplicate) {
++            pa_xfree(name);
++            continue;
++        }
++
++        pa_dynarray_append(u->stream_names, name);
++    }
++
++    return 0;
++}
++
++static int parse_common(pa_config_parser_state *state) {
++#define AUDIOGROUP_START "AudioGroup "
++#define STREAM_START "Stream "
++#define BIND_KEYWORD "bind:"
++#define NONE_KEYWORD "none"
++
++    char *section;
++    struct userdata *u = (struct userdata *) state->userdata;
++    int r;
++    pa_binding_target_info *target_info;
++
++    pa_assert(state);
++
++    section = state->section;
++    if (!section)
++        goto error;
++
++    if (strncmp(section, AUDIOGROUP_START, strlen(AUDIOGROUP_START)) == 0) {
++        char *ag_name = section + strlen(AUDIOGROUP_START);
++        struct audio_group *ag = (struct audio_group *) pa_hashmap_get(u->unused_audio_groups, ag_name);
++
++        if (!ag) {
++            /* first item for this audio group section, so create the struct */
++            ag = audio_group_new(u, ag_name);
++            pa_hashmap_put(u->unused_audio_groups, ag->id, ag);
++        }
++
++        if (strcmp(state->lvalue, "description") == 0)
++            audio_group_set_description(ag, state->rvalue);
++
++        else if (strcmp(state->lvalue, "volume-control") == 0) {
++            if (pa_streq(state->rvalue, "create"))
++                audio_group_set_volume_control_action(ag, CONTROL_ACTION_CREATE, NULL);
++
++            else if (pa_streq(state->rvalue, NONE_KEYWORD))
++                audio_group_set_volume_control_action(ag, CONTROL_ACTION_NONE, NULL);
++
++            else if (pa_startswith(state->rvalue, BIND_KEYWORD)) {
++                r = pa_binding_target_info_new_from_string(state->rvalue, "volume_control", &target_info);
++                if (r < 0) {
++                    pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue);
++                    goto error;
++                }
++
++                audio_group_set_volume_control_action(ag, CONTROL_ACTION_BIND, target_info);
++                pa_binding_target_info_free(target_info);
++
++            } else {
++                pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
++                goto error;
++            }
++        }
++        else if (strcmp(state->lvalue, "mute-control") == 0) {
++            if (pa_streq(state->rvalue, "create"))
++                audio_group_set_mute_control_action(ag, CONTROL_ACTION_CREATE, NULL);
++
++            else if (pa_streq(state->rvalue, NONE_KEYWORD))
++                audio_group_set_mute_control_action(ag, CONTROL_ACTION_NONE, NULL);
++
++            else if (pa_startswith(state->rvalue, BIND_KEYWORD)) {
++                r = pa_binding_target_info_new_from_string(state->rvalue, "mute_control", &target_info);
++                if (r < 0) {
++                    pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue);
++                    goto error;
++                }
++
++                audio_group_set_mute_control_action(ag, CONTROL_ACTION_BIND, target_info);
++                pa_binding_target_info_free(target_info);
++
++            } else {
++                pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
++                goto error;
++            }
++        }
++    }
++    else if (strncmp(section, STREAM_START, strlen(STREAM_START)) == 0) {
++        char *stream_name = section + strlen(STREAM_START);
++
++        struct stream *stream = (struct stream *) pa_hashmap_get(u->unused_streams, stream_name);
++
++        if (!stream) {
++            /* first item for this stream section, so create the struct */
++            stream = stream_new(u, stream_name);
++            pa_hashmap_put(u->unused_streams, stream->id, stream);
++        }
++
++        if (pa_streq(state->lvalue, "audio-group-for-volume"))
++            stream_set_audio_group_name_for_volume(stream, *state->rvalue ? state->rvalue : NULL);
++
++        else if (pa_streq(state->lvalue, "audio-group-for-mute"))
++            stream_set_audio_group_name_for_mute(stream, *state->rvalue ? state->rvalue : NULL);
++
++        else if (strcmp(state->lvalue, "match") == 0) {
++            if (!state->rvalue)
++                goto error;
++
++            stream->rule = parse_rule(state->rvalue);
++
++            if (!stream->rule) {
++                goto error;
++            }
++        }
++    }
++
++    return 0;
++
++error:
++
++    pa_log_error("failed parsing audio group definition file");
++    return -1;
++
++#undef NONE_KEYWORD
++#undef AUDIO_GROUP_KEYWORD
++#undef BIND_KEYWORD
++#undef STREAM_START
++#undef AUDIOGROUP_START
++}
++
++static void finalize_config(struct userdata *u) {
++    const char *group_name;
++    void *state;
++    struct audio_group *group;
++    const char *stream_name;
++    unsigned idx;
++    struct stream *stream;
++
++    pa_assert(u);
++
++    PA_HASHMAP_FOREACH(group_name, u->audio_group_names, state) {
++        int r;
++
++        group = pa_hashmap_remove(u->unused_audio_groups, group_name);
++        if (!group)
++            group = audio_group_new(u, group_name);
++
++        r = audio_group_put(group);
++        if (r < 0) {
++            pa_log("Failed to create audio group %s.", group_name);
++            audio_group_free(group);
++            continue;
++        }
++
++        pa_assert_se(pa_hashmap_put(u->audio_groups, group->id, group) >= 0);
++    }
++
++    PA_HASHMAP_FOREACH(group, u->unused_audio_groups, state)
++        pa_log_debug("Audio group %s is not used.", group->id);
++
++    pa_hashmap_free(u->unused_audio_groups);
++    u->unused_audio_groups = NULL;
++
++    pa_hashmap_free(u->audio_group_names);
++    u->audio_group_names = NULL;
++
++    PA_DYNARRAY_FOREACH(stream_name, u->stream_names, idx) {
++        stream = pa_hashmap_remove(u->unused_streams, stream_name);
++        if (!stream) {
++            pa_log("Reference to undefined stream %s, ignoring.", stream_name);
++            continue;
++        }
++
++        stream_put(stream);
++        pa_dynarray_append(u->streams, stream);
++    }
++
++    PA_HASHMAP_FOREACH(stream, u->unused_streams, state)
++        pa_log_debug("Stream %s is not used.", stream->id);
++
++    pa_hashmap_free(u->unused_streams);
++    u->unused_streams = NULL;
++
++    pa_dynarray_free(u->stream_names);
++    u->stream_names = NULL;
++}
++
++static bool parse_configuration(struct userdata *u, const char *filename) {
++    FILE *f;
++    char *fn = NULL;
++
++    pa_config_item table[] = {
++        { "audio-groups", parse_audio_groups, NULL, "General" },
++        { "streams", parse_streams, NULL, "General" },
++        { NULL, parse_common, NULL, NULL },
++        { NULL, NULL, NULL, NULL },
++    };
++
++    u->audio_group_names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
++    u->unused_audio_groups = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
++                                                 (pa_free_cb_t) audio_group_free);
++    u->stream_names = pa_dynarray_new(pa_xfree);
++    u->unused_streams = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
++                                            (pa_free_cb_t) stream_free);
++
++    if (pa_is_path_absolute(filename))
++        f = pa_open_config_file(filename, NULL, NULL, &fn);
++    else {
++        char *sys_conf_file;
++
++        sys_conf_file = pa_sprintf_malloc(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "%s", filename);
++        f = pa_open_config_file(sys_conf_file, filename, NULL, &fn);
++        pa_xfree(sys_conf_file);
++    }
++
++    if (f) {
++        pa_config_parse(fn, f, table, NULL, u);
++        pa_xfree(fn);
++        fn = NULL;
++        fclose(f);
++        f = NULL;
++    }
++
++    finalize_config(u);
++
++    return true;
++}
++
++void pa__done(pa_module *m) {
++    struct userdata* u;
++
++    pa_assert(m);
++
++    u = (struct userdata *) m->userdata;
++
++    if (!u)
++        return;
++
++    if (u->new_stream_volume)
++        pa_hook_slot_free(u->new_stream_volume);
++
++    if (u->new_stream_mute)
++        pa_hook_slot_free(u->new_stream_mute);
++
++    if (u->streams)
++        pa_dynarray_free(u->streams);
++
++    if (u->audio_groups)
++        pa_hashmap_free(u->audio_groups);
++
++    if (u->api)
++        pa_volume_api_unref(u->api);
++
++    pa_xfree(u);
++}
++
++int pa__init(pa_module *m) {
++    pa_modargs *ma = NULL;
++    struct userdata *u;
++    const char *filename;
++
++    pa_assert(m);
++
++    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
++        pa_log("Failed to parse module arguments");
++        goto error;
++    }
++
++    u = m->userdata = pa_xnew0(struct userdata, 1);
++
++    if (!u)
++        goto error;
++
++    u->api = pa_volume_api_get(m->core);
++
++    if (!u->api)
++        goto error;
++
++    u->audio_groups = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
++                                          (pa_free_cb_t) audio_group_free);
++    u->streams = pa_dynarray_new((pa_free_cb_t) stream_free);
++
++    filename = pa_modargs_get_value(ma, "filename", AUDIO_GROUP_CONFIG);
++
++    if (!parse_configuration(u, filename))
++        goto error;
++
++    u->new_stream_volume = pa_hook_connect(&u->api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_VOLUME_CONTROL], PA_HOOK_EARLY, (pa_hook_cb_t) set_volume_control_cb, u);
++    u->new_stream_mute = pa_hook_connect(&u->api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_MUTE_CONTROL], PA_HOOK_EARLY, (pa_hook_cb_t) set_mute_control_cb, u);
++
++    if (!u->new_stream_volume || !u->new_stream_mute)
++        goto error;
++
++    pa_modargs_free(ma);
++
++    return 0;
++
++error:
++    pa__done(m);
++
++    if (ma)
++        pa_modargs_free(ma);
++
++    return -1;
++}
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0082-Add-module-main-volume-policy.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0082-Add-module-main-volume-policy.patch
new file mode 100644 (file)
index 0000000..52fd3ab
--- /dev/null
@@ -0,0 +1,1404 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Wed, 21 May 2014 14:13:41 +0300
+Subject: Add module-main-volume-policy
+
+Change-Id: I787141b43cafb652aa752c64ae28b6b7aa052d8e
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ Makefile.am                                        |   3 +
+ src/Makefile.am                                    |  15 +
+ src/daemon/default.pa.in                           |   4 +
+ .../main-volume-policy/main-volume-context.c       | 325 ++++++++++++
+ .../main-volume-policy/main-volume-context.h       |  75 +++
+ .../main-volume-policy/main-volume-policy.c        | 213 ++++++++
+ .../main-volume-policy.conf.example                |  20 +
+ .../main-volume-policy/main-volume-policy.h        |  72 +++
+ .../main-volume-policy/module-main-volume-policy.c | 556 +++++++++++++++++++++
+ 9 files changed, 1283 insertions(+)
+ create mode 100644 src/modules/main-volume-policy/main-volume-context.c
+ create mode 100644 src/modules/main-volume-policy/main-volume-context.h
+ create mode 100644 src/modules/main-volume-policy/main-volume-policy.c
+ create mode 100644 src/modules/main-volume-policy/main-volume-policy.conf.example
+ create mode 100644 src/modules/main-volume-policy/main-volume-policy.h
+ create mode 100644 src/modules/main-volume-policy/module-main-volume-policy.c
+
+diff --git a/Makefile.am b/Makefile.am
+index cf4a648..646b7fc 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -61,6 +61,9 @@ moduledevinternaldir   = $(includedir)/pulsemodule/pulse
+ moduledevvolumeapi_DATA = src/modules/volume-api/*.h
+ moduledevvolumeapidir   = $(includedir)/pulsemodule/modules/volume-api
++moduledevmainvolumepolicy_DATA = src/modules/main-volume-policy/*.h
++moduledevmainvolumepolicydir   = $(includedir)/pulsemodule/modules/main-volume-policy
++
+ filterdir = /etc/pulse/filter
+ filter_DATA = filter/filter_44100_48000.dat \
+             filter/filter_44100_8000.dat \
+diff --git a/src/Makefile.am b/src/Makefile.am
+index a6bb319..8fa60ec 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -1017,6 +1017,7 @@ libpulsecore_foreign_la_CFLAGS = $(AM_CFLAGS) $(FOREIGN_CFLAGS)
+ modlibexec_LTLIBRARIES = \
+               libcli.la \
++              libmain-volume-policy.la \
+               libprotocol-cli.la \
+               libprotocol-simple.la \
+               libprotocol-http.la \
+@@ -1051,6 +1052,12 @@ libcli_la_SOURCES = pulsecore/cli.c pulsecore/cli.h
+ libcli_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version
+ libcli_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la
++libmain_volume_policy_la_SOURCES = \
++              modules/main-volume-policy/main-volume-context.c modules/main-volume-policy/main-volume-context.h \
++              modules/main-volume-policy/main-volume-policy.c modules/main-volume-policy/main-volume-policy.h
++libmain_volume_policy_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version
++libmain_volume_policy_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la libvolume-api.la
++
+ libprotocol_cli_la_SOURCES = pulsecore/protocol-cli.c pulsecore/protocol-cli.h
+ libprotocol_cli_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version
+ libprotocol_cli_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la libcli.la
+@@ -1133,6 +1140,7 @@ endif
+ modlibexec_LTLIBRARIES += \
+               module-cli.la \
+               module-cli-protocol-tcp.la \
++              module-main-volume-policy.la \
+               module-simple-protocol-tcp.la \
+               module-volume-api.la \
+               module-null-sink.la \
+@@ -1426,6 +1434,7 @@ SYMDEF_FILES = \
+               module-cli-symdef.h \
+               module-cli-protocol-tcp-symdef.h \
+               module-cli-protocol-unix-symdef.h \
++              module-main-volume-policy-symdef.h \
+               module-pipe-sink-symdef.h \
+               module-pipe-source-symdef.h \
+               module-simple-protocol-tcp-symdef.h \
+@@ -1575,6 +1584,12 @@ module_cli_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_CLI $(AM_
+ module_cli_protocol_unix_la_LDFLAGS = $(MODULE_LDFLAGS)
+ module_cli_protocol_unix_la_LIBADD = $(MODULE_LIBADD) libprotocol-cli.la
++# Main volume and mute policy
++
++module_main_volume_policy_la_SOURCES = modules/main-volume-policy/module-main-volume-policy.c
++module_main_volume_policy_la_LDFLAGS = $(MODULE_LDFLAGS)
++module_main_volume_policy_la_LIBADD = $(MODULE_LIBADD) libmain-volume-policy.la libvolume-api.la
++
+ # HTTP protocol
+ module_http_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
+diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in
+index 7cf52a4..f70804c 100755
+--- a/src/daemon/default.pa.in
++++ b/src/daemon/default.pa.in
+@@ -197,6 +197,10 @@ load-module module-volume-api
+ load-module module-audio-groups
+ .endif
++.ifexists module-main-volume-policy
++load-module module-main-volume-policy
++.endif
++
+ ### Make some devices default
+ #set-default-sink output
+ #set-default-source input
+diff --git a/src/modules/main-volume-policy/main-volume-context.c b/src/modules/main-volume-policy/main-volume-context.c
+new file mode 100644
+index 0000000..7ac35c6
+--- /dev/null
++++ b/src/modules/main-volume-policy/main-volume-context.c
+@@ -0,0 +1,325 @@
++/***
++  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 "main-volume-context.h"
++
++#include <modules/volume-api/mute-control.h>
++#include <modules/volume-api/volume-control.h>
++
++int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, const char *description,
++                               pa_main_volume_context **context) {
++    pa_main_volume_context *context_local;
++    int r;
++
++    pa_assert(policy);
++    pa_assert(name);
++    pa_assert(description);
++    pa_assert(context);
++
++    context_local = pa_xnew0(struct pa_main_volume_context, 1);
++    context_local->main_volume_policy = policy;
++    context_local->index = pa_main_volume_policy_allocate_main_volume_context_index(policy);
++
++    r = pa_main_volume_policy_register_name(policy, name, true, &context_local->name);
++    if (r < 0)
++        goto fail;
++
++    context_local->description = pa_xstrdup(description);
++
++    *context = context_local;
++
++    return 0;
++
++fail:
++    pa_main_volume_context_free(context_local);
++
++    return r;
++}
++
++void pa_main_volume_context_put(pa_main_volume_context *context) {
++    pa_assert(context);
++
++    pa_main_volume_policy_add_main_volume_context(context->main_volume_policy, context);
++
++    context->linked = true;
++
++    pa_log_debug("Created main volume context #%u.", context->index);
++    pa_log_debug("    Name: %s", context->name);
++    pa_log_debug("    Description: %s", context->description);
++    pa_log_debug("    Main output volume control: %s",
++                 context->main_output_volume_control ? context->main_output_volume_control->name : "(unset)");
++    pa_log_debug("    Main input volume control: %s",
++                 context->main_input_volume_control ? context->main_input_volume_control->name : "(unset)");
++    pa_log_debug("    Main output mute control: %s",
++                 context->main_output_mute_control ? context->main_output_mute_control->name : "(unset)");
++    pa_log_debug("    Main input mute control: %s",
++                 context->main_input_mute_control ? context->main_input_mute_control->name : "(unset)");
++
++    pa_hook_fire(&context->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_PUT], context);
++}
++
++void pa_main_volume_context_unlink(pa_main_volume_context *context) {
++    pa_assert(context);
++
++    if (context->unlinked) {
++        pa_log_debug("Unlinking main volume context %s (already unlinked, this is a no-op).", context->name);
++        return;
++    }
++
++    context->unlinked = true;
++
++    pa_log_debug("Unlinking main volume context %s.", context->name);
++
++    if (context->linked)
++        pa_hook_fire(&context->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK], context);
++
++    if (context->main_input_mute_control_binding) {
++        pa_binding_free(context->main_input_mute_control_binding);
++        context->main_input_mute_control_binding = NULL;
++    }
++
++    if (context->main_output_mute_control_binding) {
++        pa_binding_free(context->main_output_mute_control_binding);
++        context->main_output_mute_control_binding = NULL;
++    }
++
++    if (context->main_input_volume_control_binding) {
++        pa_binding_free(context->main_input_volume_control_binding);
++        context->main_input_volume_control_binding = NULL;
++    }
++
++    if (context->main_output_volume_control_binding) {
++        pa_binding_free(context->main_output_volume_control_binding);
++        context->main_output_volume_control_binding = NULL;
++    }
++
++    context->main_input_mute_control = NULL;
++    context->main_output_mute_control = NULL;
++    context->main_input_volume_control = NULL;
++    context->main_output_volume_control = NULL;
++
++    pa_main_volume_policy_remove_main_volume_context(context->main_volume_policy, context);
++}
++
++void pa_main_volume_context_free(pa_main_volume_context *context) {
++    pa_assert(context);
++
++    if (!context->unlinked)
++        pa_main_volume_context_unlink(context);
++
++    pa_xfree(context->description);
++
++    if (context->name)
++        pa_main_volume_policy_unregister_name(context->main_volume_policy, context->name);
++
++    pa_xfree(context);
++}
++
++const char *pa_main_volume_context_get_name(pa_main_volume_context *context) {
++    pa_assert(context);
++
++    return context->name;
++}
++
++static void set_main_output_volume_control_internal(pa_main_volume_context *context, pa_volume_control *control) {
++    pa_volume_control *old_control;
++
++    pa_assert(context);
++
++    old_control = context->main_output_volume_control;
++
++    if (control == old_control)
++        return;
++
++    context->main_output_volume_control = control;
++
++    if (!context->linked || context->unlinked)
++        return;
++
++    pa_log_debug("The main output volume control of main volume context %s changed from %s to %s.", context->name,
++                 old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
++
++    pa_hook_fire(&context->main_volume_policy->hooks
++                     [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED],
++                 context);
++}
++
++void pa_main_volume_context_bind_main_output_volume_control(pa_main_volume_context *context,
++                                                            pa_binding_target_info *target_info) {
++    pa_binding_owner_info owner_info = {
++        .userdata = context,
++        .set_value = (pa_binding_set_value_cb_t) set_main_output_volume_control_internal,
++    };
++
++    pa_assert(context);
++    pa_assert(target_info);
++
++    if (context->main_output_volume_control_binding)
++        pa_binding_free(context->main_output_volume_control_binding);
++
++    context->main_output_volume_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
++                                                                 target_info);
++}
++
++static void set_main_input_volume_control_internal(pa_main_volume_context *context, pa_volume_control *control) {
++    pa_volume_control *old_control;
++
++    pa_assert(context);
++
++    old_control = context->main_input_volume_control;
++
++    if (control == old_control)
++        return;
++
++    context->main_input_volume_control = control;
++
++    if (!context->linked || context->unlinked)
++        return;
++
++    pa_log_debug("The main input volume control of main volume context %s changed from %s to %s.", context->name,
++                 old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
++
++    pa_hook_fire(&context->main_volume_policy->hooks
++                     [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_VOLUME_CONTROL_CHANGED],
++                 context);
++}
++
++void pa_main_volume_context_bind_main_input_volume_control(pa_main_volume_context *context,
++                                                           pa_binding_target_info *target_info) {
++    pa_binding_owner_info owner_info = {
++        .userdata = context,
++        .set_value = (pa_binding_set_value_cb_t) set_main_input_volume_control_internal,
++    };
++
++    pa_assert(context);
++    pa_assert(target_info);
++
++    if (context->main_input_volume_control_binding)
++        pa_binding_free(context->main_input_volume_control_binding);
++
++    context->main_input_volume_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
++                                                                target_info);
++}
++
++static void set_main_output_mute_control_internal(pa_main_volume_context *context, pa_mute_control *control) {
++    pa_mute_control *old_control;
++
++    pa_assert(context);
++
++    old_control = context->main_output_mute_control;
++
++    if (control == old_control)
++        return;
++
++    context->main_output_mute_control = control;
++
++    if (!context->linked || context->unlinked)
++        return;
++
++    pa_log_debug("The main output mute control of main volume context %s changed from %s to %s.", context->name,
++                 old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
++
++    pa_hook_fire(&context->main_volume_policy->hooks
++                     [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_MUTE_CONTROL_CHANGED],
++                 context);
++}
++
++void pa_main_volume_context_bind_main_output_mute_control(pa_main_volume_context *context,
++                                                          pa_binding_target_info *target_info) {
++    pa_binding_owner_info owner_info = {
++        .userdata = context,
++        .set_value = (pa_binding_set_value_cb_t) set_main_output_mute_control_internal,
++    };
++
++    pa_assert(context);
++    pa_assert(target_info);
++
++    if (context->main_output_mute_control_binding)
++        pa_binding_free(context->main_output_mute_control_binding);
++
++    context->main_output_mute_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
++                                                               target_info);
++}
++
++static void set_main_input_mute_control_internal(pa_main_volume_context *context, pa_mute_control *control) {
++    pa_mute_control *old_control;
++
++    pa_assert(context);
++
++    old_control = context->main_input_mute_control;
++
++    if (control == old_control)
++        return;
++
++    context->main_input_mute_control = control;
++
++    if (!context->linked || context->unlinked)
++        return;
++
++    pa_log_debug("The main input mute control of main volume context %s changed from %s to %s.", context->name,
++                 old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
++
++    pa_hook_fire(&context->main_volume_policy->hooks
++                     [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_MUTE_CONTROL_CHANGED],
++                 context);
++}
++
++void pa_main_volume_context_bind_main_input_mute_control(pa_main_volume_context *context,
++                                                         pa_binding_target_info *target_info) {
++    pa_binding_owner_info owner_info = {
++        .userdata = context,
++        .set_value = (pa_binding_set_value_cb_t) set_main_input_mute_control_internal,
++    };
++
++    pa_assert(context);
++    pa_assert(target_info);
++
++    if (context->main_input_mute_control_binding)
++        pa_binding_free(context->main_input_mute_control_binding);
++
++    context->main_input_mute_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
++                                                              target_info);
++}
++
++pa_binding_target_type *pa_main_volume_context_create_binding_target_type(pa_main_volume_policy *policy) {
++    pa_binding_target_type *type;
++
++    pa_assert(policy);
++
++    type = pa_binding_target_type_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, policy->main_volume_contexts,
++                                      &policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_PUT],
++                                      &policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK],
++                                      (pa_binding_target_type_get_name_cb_t) pa_main_volume_context_get_name);
++    pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL,
++                                     PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_output_volume_control));
++    pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL,
++                                     PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_input_volume_control));
++    pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL,
++                                     PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_output_mute_control));
++    pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL,
++                                     PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_input_mute_control));
++
++    return type;
++}
+diff --git a/src/modules/main-volume-policy/main-volume-context.h b/src/modules/main-volume-policy/main-volume-context.h
+new file mode 100644
+index 0000000..4a0a6f7
+--- /dev/null
++++ b/src/modules/main-volume-policy/main-volume-context.h
+@@ -0,0 +1,75 @@
++#ifndef foomainvolumecontexthfoo
++#define foomainvolumecontexthfoo
++
++/***
++  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/main-volume-policy/main-volume-policy.h>
++
++#include <modules/volume-api/binding.h>
++
++typedef struct pa_main_volume_context pa_main_volume_context;
++
++#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE "MainVolumeContext"
++#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL "main_output_volume_control"
++#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL "main_input_volume_control"
++#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL "main_output_mute_control"
++#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL "main_input_mute_control"
++
++struct pa_main_volume_context {
++    pa_main_volume_policy *main_volume_policy;
++    uint32_t index;
++    const char *name;
++    char *description;
++    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;
++
++    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;
++
++    bool linked;
++    bool unlinked;
++};
++
++int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, const char *description,
++                               pa_main_volume_context **context);
++void pa_main_volume_context_put(pa_main_volume_context *context);
++void pa_main_volume_context_unlink(pa_main_volume_context *context);
++void pa_main_volume_context_free(pa_main_volume_context *context);
++
++const char *pa_main_volume_context_get_name(pa_main_volume_context *context);
++
++void pa_main_volume_context_bind_main_output_volume_control(pa_main_volume_context *context,
++                                                            pa_binding_target_info *target_info);
++void pa_main_volume_context_bind_main_input_volume_control(pa_main_volume_context *context,
++                                                           pa_binding_target_info *target_info);
++void pa_main_volume_context_bind_main_output_mute_control(pa_main_volume_context *context,
++                                                          pa_binding_target_info *target_info);
++void pa_main_volume_context_bind_main_input_mute_control(pa_main_volume_context *context, pa_binding_target_info *target_info);
++
++/* Called from main-volume-policy.c only. */
++pa_binding_target_type *pa_main_volume_context_create_binding_target_type(pa_main_volume_policy *policy);
++
++#endif
+diff --git a/src/modules/main-volume-policy/main-volume-policy.c b/src/modules/main-volume-policy/main-volume-policy.c
+new file mode 100644
+index 0000000..b0b4ede
+--- /dev/null
++++ b/src/modules/main-volume-policy/main-volume-policy.c
+@@ -0,0 +1,213 @@
++/***
++  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 "main-volume-policy.h"
++
++#include <modules/main-volume-policy/main-volume-context.h>
++
++#include <pulsecore/core-util.h>
++#include <pulsecore/shared.h>
++
++static pa_main_volume_policy *main_volume_policy_new(pa_core *core);
++static void main_volume_policy_free(pa_main_volume_policy *policy);
++
++pa_main_volume_policy *pa_main_volume_policy_get(pa_core *core) {
++    pa_main_volume_policy *policy;
++
++    pa_assert(core);
++
++    policy = pa_shared_get(core, "main-volume-policy");
++
++    if (policy)
++        pa_main_volume_policy_ref(policy);
++    else {
++        policy = main_volume_policy_new(core);
++        pa_assert_se(pa_shared_set(core, "main-volume-policy", policy) >= 0);
++    }
++
++    return policy;
++}
++
++pa_main_volume_policy *pa_main_volume_policy_ref(pa_main_volume_policy *policy) {
++    pa_assert(policy);
++
++    policy->refcnt++;
++
++    return policy;
++}
++
++void pa_main_volume_policy_unref(pa_main_volume_policy *policy) {
++    pa_assert(policy);
++    pa_assert(policy->refcnt > 0);
++
++    policy->refcnt--;
++
++    if (policy->refcnt == 0) {
++        pa_assert_se(pa_shared_remove(policy->core, "main-volume-policy") >= 0);
++        main_volume_policy_free(policy);
++    }
++}
++
++static pa_main_volume_policy *main_volume_policy_new(pa_core *core) {
++    pa_main_volume_policy *policy;
++    unsigned i;
++
++    pa_assert(core);
++
++    policy = pa_xnew0(pa_main_volume_policy, 1);
++    policy->core = core;
++    policy->refcnt = 1;
++    policy->volume_api = pa_volume_api_get(core);
++    policy->names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
++    policy->main_volume_contexts = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
++
++    for (i = 0; i < PA_MAIN_VOLUME_POLICY_HOOK_MAX; i++)
++        pa_hook_init(&policy->hooks[i], policy);
++
++    policy->main_volume_context_binding_target_type = pa_main_volume_context_create_binding_target_type(policy);
++    pa_volume_api_add_binding_target_type(policy->volume_api, policy->main_volume_context_binding_target_type);
++
++    pa_log_debug("Created a pa_main_volume_policy object.");
++
++    return policy;
++}
++
++static void main_volume_policy_free(pa_main_volume_policy *policy) {
++    unsigned i;
++
++    pa_assert(policy);
++    pa_assert(policy->refcnt == 0);
++
++    pa_log_debug("Freeing the pa_main_volume_policy object.");
++
++    if (policy->main_volume_context_binding_target_type) {
++        pa_volume_api_remove_binding_target_type(policy->volume_api, policy->main_volume_context_binding_target_type);
++        pa_binding_target_type_free(policy->main_volume_context_binding_target_type);
++    }
++
++    for (i = 0; i < PA_MAIN_VOLUME_POLICY_HOOK_MAX; i++)
++        pa_hook_done(&policy->hooks[i]);
++
++    if (policy->main_volume_contexts) {
++        pa_assert(pa_hashmap_isempty(policy->main_volume_contexts));
++        pa_hashmap_free(policy->main_volume_contexts);
++    }
++
++    if (policy->names) {
++        pa_assert(pa_hashmap_isempty(policy->names));
++        pa_hashmap_free(policy->names);
++    }
++
++    if (policy->volume_api)
++        pa_volume_api_unref(policy->volume_api);
++
++    pa_xfree(policy);
++}
++
++int pa_main_volume_policy_register_name(pa_main_volume_policy *policy, const char *requested_name,
++                                        bool fail_if_already_registered, const char **registered_name) {
++    char *n;
++
++    pa_assert(policy);
++    pa_assert(requested_name);
++    pa_assert(registered_name);
++
++    n = pa_xstrdup(requested_name);
++
++    if (pa_hashmap_put(policy->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(policy->names, n, n) < 0);
++    }
++
++    *registered_name = n;
++
++    return 0;
++}
++
++void pa_main_volume_policy_unregister_name(pa_main_volume_policy *policy, const char *name) {
++    pa_assert(policy);
++    pa_assert(name);
++
++    pa_assert_se(pa_hashmap_remove_and_free(policy->names, name) >= 0);
++}
++
++uint32_t pa_main_volume_policy_allocate_main_volume_context_index(pa_main_volume_policy *policy) {
++    uint32_t idx;
++
++    pa_assert(policy);
++
++    idx = policy->next_main_volume_context_index++;
++
++    return idx;
++}
++
++void pa_main_volume_policy_add_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context) {
++    pa_assert(policy);
++    pa_assert(context);
++
++    pa_assert_se(pa_hashmap_put(policy->main_volume_contexts, (void *) context->name, context) >= 0);
++}
++
++int pa_main_volume_policy_remove_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context) {
++    pa_assert(policy);
++    pa_assert(context);
++
++    if (!pa_hashmap_remove(policy->main_volume_contexts, context->name))
++        return -1;
++
++    if (context == policy->active_main_volume_context)
++        pa_main_volume_policy_set_active_main_volume_context(policy, NULL);
++
++    return 0;
++}
++
++void pa_main_volume_policy_set_active_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context) {
++    pa_main_volume_context *old_context;
++
++    pa_assert(policy);
++
++    old_context = policy->active_main_volume_context;
++
++    if (context == old_context)
++        return;
++
++    policy->active_main_volume_context = context;
++
++    pa_log_debug("The active main volume context changed from %s to %s.", old_context ? old_context->name : "(unset)",
++                 context ? context->name : "(unset)");
++
++    pa_hook_fire(&policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_ACTIVE_MAIN_VOLUME_CONTEXT_CHANGED], NULL);
++}
+diff --git a/src/modules/main-volume-policy/main-volume-policy.conf.example b/src/modules/main-volume-policy/main-volume-policy.conf.example
+new file mode 100644
+index 0000000..a4a35d3
+--- /dev/null
++++ b/src/modules/main-volume-policy/main-volume-policy.conf.example
+@@ -0,0 +1,20 @@
++[General]
++output-volume-model = by-active-main-volume-context
++input-volume-model = by-active-main-volume-context
++output-mute-model = none
++input-mute-model = none
++main-volume-contexts = x-example-call-main-volume-context x-example-default-main-volume-context
++
++[MainVolumeContext x-example-call-main-volume-context]
++description = Call main volume context
++main-output-volume-control = bind:AudioGroup:x-example-call-downlink-audio-group
++main-input-volume-control = bind:AudioGroup:x-example-call-uplink-audio-group
++main-output-mute-control = none
++main-input-mute-control = none
++
++[MainVolumeContext x-example-default-main-volume-context]
++description = Default main volume context
++main-output-volume-control = bind:AudioGroup:x-example-default-output-audio-group
++main-input-volume-control = bind:AudioGroup:x-example-default-input-audio-group
++main-output-mute-control = none
++main-input-mute-control = none
+diff --git a/src/modules/main-volume-policy/main-volume-policy.h b/src/modules/main-volume-policy/main-volume-policy.h
+new file mode 100644
+index 0000000..5cd669e
+--- /dev/null
++++ b/src/modules/main-volume-policy/main-volume-policy.h
+@@ -0,0 +1,72 @@
++#ifndef foomainvolumepolicyhfoo
++#define foomainvolumepolicyhfoo
++
++/***
++  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/volume-api.h>
++
++#include <pulsecore/core.h>
++
++typedef struct pa_main_volume_policy pa_main_volume_policy;
++
++/* Avoid circular dependencies... */
++typedef struct pa_main_volume_context pa_main_volume_context;
++
++enum {
++    PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_PUT,
++    PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK,
++    PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED,
++    PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_VOLUME_CONTROL_CHANGED,
++    PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_MUTE_CONTROL_CHANGED,
++    PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_MUTE_CONTROL_CHANGED,
++    PA_MAIN_VOLUME_POLICY_HOOK_ACTIVE_MAIN_VOLUME_CONTEXT_CHANGED,
++    PA_MAIN_VOLUME_POLICY_HOOK_MAX,
++};
++
++struct pa_main_volume_policy {
++    pa_core *core;
++    unsigned refcnt;
++    pa_volume_api *volume_api;
++    pa_hashmap *names; /* object name -> object name (hashmap-as-a-set) */
++    pa_hashmap *main_volume_contexts; /* name -> pa_main_volume_context */
++    pa_main_volume_context *active_main_volume_context;
++
++    uint32_t next_main_volume_context_index;
++    pa_hook hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAX];
++    pa_binding_target_type *main_volume_context_binding_target_type;
++};
++
++pa_main_volume_policy *pa_main_volume_policy_get(pa_core *core);
++pa_main_volume_policy *pa_main_volume_policy_ref(pa_main_volume_policy *policy);
++void pa_main_volume_policy_unref(pa_main_volume_policy *policy);
++
++int pa_main_volume_policy_register_name(pa_main_volume_policy *policy, const char *requested_name,
++                                        bool fail_if_already_registered, const char **registered_name);
++void pa_main_volume_policy_unregister_name(pa_main_volume_policy *policy, const char *name);
++
++uint32_t pa_main_volume_policy_allocate_main_volume_context_index(pa_main_volume_policy *policy);
++void pa_main_volume_policy_add_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context);
++int pa_main_volume_policy_remove_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context);
++void pa_main_volume_policy_set_active_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context);
++
++#endif
+diff --git a/src/modules/main-volume-policy/module-main-volume-policy.c b/src/modules/main-volume-policy/module-main-volume-policy.c
+new file mode 100644
+index 0000000..a14699d
+--- /dev/null
++++ b/src/modules/main-volume-policy/module-main-volume-policy.c
+@@ -0,0 +1,556 @@
++/***
++  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 "module-main-volume-policy-symdef.h"
++
++#include <modules/main-volume-policy/main-volume-context.h>
++
++#include <modules/volume-api/binding.h>
++#include <modules/volume-api/volume-api.h>
++
++#include <pulse/direction.h>
++
++#include <pulsecore/conf-parser.h>
++#include <pulsecore/core-util.h>
++#include <pulsecore/i18n.h>
++
++PA_MODULE_AUTHOR("Tanu Kaskinen");
++PA_MODULE_DESCRIPTION(_("Main volume and mute policy"));
++PA_MODULE_VERSION(PACKAGE_VERSION);
++PA_MODULE_LOAD_ONCE(true);
++
++enum control_type {
++    CONTROL_TYPE_VOLUME,
++    CONTROL_TYPE_MUTE,
++};
++
++enum model {
++    MODEL_NONE,
++    MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT,
++};
++
++struct userdata {
++    pa_main_volume_policy *main_volume_policy;
++    enum model output_volume_model;
++    enum model input_volume_model;
++    enum model output_mute_model;
++    enum model input_mute_model;
++    pa_hashmap *contexts; /* name -> struct context */
++
++    pa_hook_slot *active_main_volume_context_changed_slot;
++
++    /* The following fields are only used during initialization. */
++    pa_hashmap *context_names; /* name -> name (hashmap-as-a-set) */
++    pa_hashmap *unused_contexts; /* name -> struct context */
++};
++
++struct context {
++    struct userdata *userdata;
++    char *name;
++    char *description;
++    pa_binding_target_info *main_output_volume_control_target_info;
++    pa_binding_target_info *main_input_volume_control_target_info;
++    pa_binding_target_info *main_output_mute_control_target_info;
++    pa_binding_target_info *main_input_mute_control_target_info;
++    pa_main_volume_context *main_volume_context;
++
++    bool unlinked;
++};
++
++static void context_unlink(struct context *context);
++
++static const char *model_to_string(enum model model) {
++    switch (model) {
++        case MODEL_NONE:
++            return "none";
++
++        case MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT:
++            return "by-active-main-volume-context";
++    }
++
++    pa_assert_not_reached();
++}
++
++static int model_from_string(const char *str, enum model *model) {
++    pa_assert(str);
++    pa_assert(model);
++
++    if (pa_streq(str, "none"))
++        *model = MODEL_NONE;
++    else if (pa_streq(str, "by-active-main-volume-context"))
++        *model = MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT;
++    else
++        return -PA_ERR_INVALID;
++
++    return 0;
++}
++
++static struct context *context_new(struct userdata *u, const char *name) {
++    struct context *context;
++
++    pa_assert(u);
++    pa_assert(name);
++
++    context = pa_xnew0(struct context, 1);
++    context->userdata = u;
++    context->name = pa_xstrdup(name);
++    context->description = pa_xstrdup(name);
++
++    return context;
++}
++
++static int context_put(struct context *context) {
++    int r;
++
++    pa_assert(context);
++
++    r = pa_main_volume_context_new(context->userdata->main_volume_policy, context->name, context->description,
++                                   &context->main_volume_context);
++    if (r < 0)
++        goto fail;
++
++    if (context->main_output_volume_control_target_info)
++        pa_main_volume_context_bind_main_output_volume_control(context->main_volume_context,
++                                                               context->main_output_volume_control_target_info);
++
++    if (context->main_input_volume_control_target_info)
++        pa_main_volume_context_bind_main_input_volume_control(context->main_volume_context,
++                                                              context->main_input_volume_control_target_info);
++
++    if (context->main_output_mute_control_target_info)
++        pa_main_volume_context_bind_main_output_mute_control(context->main_volume_context,
++                                                             context->main_output_mute_control_target_info);
++
++    if (context->main_input_mute_control_target_info)
++        pa_main_volume_context_bind_main_input_mute_control(context->main_volume_context,
++                                                            context->main_input_mute_control_target_info);
++
++    pa_main_volume_context_put(context->main_volume_context);
++
++    return 0;
++
++fail:
++    context_unlink(context);
++
++    return r;
++}
++
++static void context_unlink(struct context *context) {
++    pa_assert(context);
++
++    if (context->unlinked)
++        return;
++
++    context->unlinked = true;
++
++    if (context->main_volume_context) {
++        pa_main_volume_context_free(context->main_volume_context);
++        context->main_volume_context = NULL;
++    }
++}
++
++static void context_free(struct context *context) {
++    pa_assert(context);
++
++    if (!context->unlinked)
++        context_unlink(context);
++
++    if (context->main_input_mute_control_target_info)
++        pa_binding_target_info_free(context->main_input_mute_control_target_info);
++
++    if (context->main_output_mute_control_target_info)
++        pa_binding_target_info_free(context->main_output_mute_control_target_info);
++
++    if (context->main_input_volume_control_target_info)
++        pa_binding_target_info_free(context->main_input_volume_control_target_info);
++
++    if (context->main_output_volume_control_target_info)
++        pa_binding_target_info_free(context->main_output_volume_control_target_info);
++
++    pa_xfree(context->description);
++    pa_xfree(context->name);
++    pa_xfree(context);
++}
++
++static void context_set_description(struct context *context, const char *description) {
++    pa_assert(context);
++    pa_assert(description);
++
++    pa_xfree(context->description);
++    context->description = pa_xstrdup(description);
++}
++
++static void context_set_main_control_target_info(struct context *context, enum control_type type, pa_direction_t direction,
++                                                 pa_binding_target_info *info) {
++    pa_assert(context);
++
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            if (direction == PA_DIRECTION_OUTPUT) {
++                if (context->main_output_volume_control_target_info)
++                    pa_binding_target_info_free(context->main_output_volume_control_target_info);
++
++                if (info)
++                    context->main_output_volume_control_target_info = pa_binding_target_info_copy(info);
++                else
++                    context->main_output_volume_control_target_info = NULL;
++            } else {
++                if (context->main_input_volume_control_target_info)
++                    pa_binding_target_info_free(context->main_input_volume_control_target_info);
++
++                if (info)
++                    context->main_input_volume_control_target_info = pa_binding_target_info_copy(info);
++                else
++                    context->main_input_volume_control_target_info = NULL;
++            }
++            break;
++
++        case CONTROL_TYPE_MUTE:
++            if (direction == PA_DIRECTION_OUTPUT) {
++                if (context->main_output_mute_control_target_info)
++                    pa_binding_target_info_free(context->main_output_mute_control_target_info);
++
++                if (info)
++                    context->main_output_mute_control_target_info = pa_binding_target_info_copy(info);
++                else
++                    context->main_output_mute_control_target_info = NULL;
++            } else {
++                if (context->main_input_mute_control_target_info)
++                    pa_binding_target_info_free(context->main_input_mute_control_target_info);
++
++                if (info)
++                    context->main_input_mute_control_target_info = pa_binding_target_info_copy(info);
++                else
++                    context->main_input_mute_control_target_info = NULL;
++            }
++            break;
++    }
++}
++
++static pa_hook_result_t active_main_volume_context_changed_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_main_volume_context *context;
++    pa_volume_api *api;
++    pa_binding_target_info *info = NULL;
++
++    pa_assert(u);
++
++    context = u->main_volume_policy->active_main_volume_context;
++    api = u->main_volume_policy->volume_api;
++
++    if (u->output_volume_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
++        if (context) {
++            info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
++                                              PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL);
++            pa_volume_api_bind_main_output_volume_control(api, info);
++        } else
++            pa_volume_api_set_main_output_volume_control(api, NULL);
++    }
++
++    if (u->input_volume_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
++        if (context) {
++            info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
++                                              PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL);
++            pa_volume_api_bind_main_input_volume_control(api, info);
++        } else
++            pa_volume_api_set_main_input_volume_control(api, NULL);
++    }
++
++    if (u->output_mute_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
++        if (context) {
++            info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
++                                              PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL);
++            pa_volume_api_bind_main_output_mute_control(api, info);
++        } else
++            pa_volume_api_set_main_output_mute_control(api, NULL);
++    }
++
++    if (u->input_mute_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
++        if (context) {
++            info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
++                                              PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL);
++            pa_volume_api_bind_main_input_mute_control(api, info);
++        } else
++            pa_volume_api_set_main_input_mute_control(api, NULL);
++    }
++
++    if (info)
++        pa_binding_target_info_free(info);
++
++    return PA_HOOK_OK;
++}
++
++static int parse_model(pa_config_parser_state *state) {
++    int r;
++
++    pa_assert(state);
++
++    r = model_from_string(state->rvalue, state->data);
++    if (r < 0)
++        pa_log("[%s:%u] Failed to parse model: %s", state->filename, state->lineno, state->rvalue);
++
++    return r;
++}
++
++static int parse_main_volume_contexts(pa_config_parser_state *state) {
++    struct userdata *u;
++    char *name;
++    const char *split_state = NULL;
++
++    pa_assert(state);
++
++    u = state->userdata;
++
++    while ((name = pa_split_spaces(state->rvalue, &split_state)))
++        pa_hashmap_put(u->context_names, name, name);
++
++    return 0;
++}
++
++static struct context *get_context(struct userdata *u, const char *section) {
++    const char *name;
++    struct context *context;
++
++    pa_assert(u);
++
++    if (!section)
++        return NULL;
++
++    if (!pa_startswith(section, "MainVolumeContext "))
++        return NULL;
++
++    name = section + 18;
++
++    context = pa_hashmap_get(u->unused_contexts, name);
++    if (!context) {
++        context = context_new(u, name);
++        pa_hashmap_put(u->unused_contexts, context->name, context);
++    }
++
++    return context;
++}
++
++static int parse_description(pa_config_parser_state *state) {
++    struct userdata *u;
++    struct context *context;
++
++    pa_assert(state);
++
++    u = state->userdata;
++
++    context = get_context(u, state->section);
++    if (!context) {
++        pa_log("[%s:%u] Key \"%s\" not expected in section %s.", state->filename, state->lineno, state->lvalue,
++               pa_strnull(state->section));
++        return -PA_ERR_INVALID;
++    }
++
++    context_set_description(context, state->rvalue);
++
++    return 0;
++}
++
++static const char *get_target_field_name(enum control_type type) {
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            return "volume_control";
++
++        case CONTROL_TYPE_MUTE:
++            return "mute_control";
++    }
++
++    pa_assert_not_reached();
++}
++
++static int parse_main_control(pa_config_parser_state *state, enum control_type type, pa_direction_t direction) {
++    struct userdata *u;
++    struct context *context;
++
++    pa_assert(state);
++
++    u = state->userdata;
++
++    context = get_context(u, state->section);
++    if (!context) {
++        pa_log("[%s:%u] Key \"%s\" not expected in section %s.", state->filename, state->lineno, state->lvalue,
++               pa_strnull(state->section));
++        return -PA_ERR_INVALID;
++    }
++
++    if (pa_streq(state->rvalue, "none"))
++        context_set_main_control_target_info(context, type, direction, NULL);
++    else if (pa_startswith(state->rvalue, "bind:")) {
++        int r;
++        pa_binding_target_info *info;
++
++        r = pa_binding_target_info_new_from_string(state->rvalue, get_target_field_name(type), &info);
++        if (r < 0) {
++            pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue);
++            return r;
++        }
++
++        context_set_main_control_target_info(context, type, direction, info);
++        pa_binding_target_info_free(info);
++    } else {
++        pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
++        return -PA_ERR_INVALID;
++    }
++
++    return 0;
++}
++
++static int parse_main_output_volume_control(pa_config_parser_state *state) {
++    pa_assert(state);
++
++    return parse_main_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT);
++}
++
++static int parse_main_input_volume_control(pa_config_parser_state *state) {
++    pa_assert(state);
++
++    return parse_main_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT);
++}
++
++static int parse_main_output_mute_control(pa_config_parser_state *state) {
++    pa_assert(state);
++
++    return parse_main_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT);
++}
++
++static int parse_main_input_mute_control(pa_config_parser_state *state) {
++    pa_assert(state);
++
++    return parse_main_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT);
++}
++
++static void finalize_config(struct userdata *u) {
++    const char *context_name;
++    void *state;
++    struct context *context;
++
++    pa_assert(u);
++
++    PA_HASHMAP_FOREACH(context_name, u->context_names, state) {
++        int r;
++
++        context = pa_hashmap_remove(u->unused_contexts, context_name);
++        if (!context)
++            context = context_new(u, context_name);
++
++        r = context_put(context);
++        if (r < 0) {
++            pa_log_warn("Failed to create main volume context %s.", context_name);
++            context_free(context);
++            continue;
++        }
++
++        pa_assert_se(pa_hashmap_put(u->contexts, context->name, context) >= 0);
++    }
++
++    PA_HASHMAP_FOREACH(context, u->unused_contexts, state)
++        pa_log_debug("Main volume context %s is not used.", context->name);
++
++    pa_hashmap_free(u->unused_contexts);
++    u->unused_contexts = NULL;
++
++    pa_hashmap_free(u->context_names);
++    u->context_names = NULL;
++}
++
++int pa__init(pa_module *module) {
++    struct userdata *u;
++    FILE *f;
++    char *fn = NULL;
++
++    pa_assert(module);
++
++    u = module->userdata = pa_xnew0(struct userdata, 1);
++    u->main_volume_policy = pa_main_volume_policy_get(module->core);
++    u->output_volume_model = MODEL_NONE;
++    u->input_volume_model = MODEL_NONE;
++    u->output_mute_model = MODEL_NONE;
++    u->input_mute_model = MODEL_NONE;
++    u->contexts = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
++                                      (pa_free_cb_t) context_free);
++    u->active_main_volume_context_changed_slot =
++            pa_hook_connect(&u->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_ACTIVE_MAIN_VOLUME_CONTEXT_CHANGED],
++                            PA_HOOK_NORMAL, active_main_volume_context_changed_cb, u);
++    u->context_names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
++    u->unused_contexts = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
++                                             (pa_free_cb_t) context_free);
++
++    f = pa_open_config_file(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "main-volume-policy.conf", "main-volume-policy.conf", NULL, &fn);
++    if (f) {
++        pa_config_item config_items[] = {
++            { "output-volume-model", parse_model, &u->output_volume_model, "General" },
++            { "input-volume-model", parse_model, &u->input_volume_model, "General" },
++            { "output-mute-model", parse_model, &u->output_mute_model, "General" },
++            { "input-mute-model", parse_model, &u->input_mute_model, "General" },
++            { "main-volume-contexts", parse_main_volume_contexts, NULL, "General" },
++            { "description", parse_description, NULL, NULL },
++            { "main-output-volume-control", parse_main_output_volume_control, NULL, NULL },
++            { "main-input-volume-control", parse_main_input_volume_control, NULL, NULL },
++            { "main-output-mute-control", parse_main_output_mute_control, NULL, NULL },
++            { "main-input-mute-control", parse_main_input_mute_control, NULL, NULL },
++            { NULL },
++        };
++
++        pa_config_parse(fn, f, config_items, NULL, u);
++        pa_xfree(fn);
++        fn = NULL;
++        fclose(f);
++        f = NULL;
++    }
++
++    finalize_config(u);
++
++    pa_log_debug("Output volume model: %s", model_to_string(u->output_volume_model));
++    pa_log_debug("Input volume model: %s", model_to_string(u->input_volume_model));
++    pa_log_debug("Output mute model: %s", model_to_string(u->output_mute_model));
++    pa_log_debug("Input mute model: %s", model_to_string(u->input_mute_model));
++
++    return 0;
++}
++
++void pa__done(pa_module *module) {
++    struct userdata *u;
++
++    pa_assert(module);
++
++    u = module->userdata;
++    if (!u)
++        return;
++
++    if (u->active_main_volume_context_changed_slot)
++        pa_hook_slot_free(u->active_main_volume_context_changed_slot);
++
++    if (u->contexts)
++        pa_hashmap_free(u->contexts);
++
++    if (u->main_volume_policy)
++        pa_main_volume_policy_unref(u->main_volume_policy);
++
++    pa_xfree(u);
++}
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0083-configuration-Add-default-IVI-audio-group-and-main-v.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0083-configuration-Add-default-IVI-audio-group-and-main-v.patch
new file mode 100644 (file)
index 0000000..5eed75c
--- /dev/null
@@ -0,0 +1,119 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Thu, 22 May 2014 14:43:33 +0300
+Subject: configuration: Add default IVI audio group and main volume
+ configuration
+
+Change-Id: Idd348cc9f469e988405d574dbc2459c5822a33c2
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/Makefile.am                                | 21 ++++++++++++++++
+ src/tizen-ivi-examples/audio-groups.conf       | 33 ++++++++++++++++++++++++++
+ src/tizen-ivi-examples/main-volume-policy.conf | 20 ++++++++++++++++
+ 3 files changed, 74 insertions(+)
+ create mode 100644 src/tizen-ivi-examples/audio-groups.conf
+ create mode 100644 src/tizen-ivi-examples/main-volume-policy.conf
+
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 8fa60ec..d57c30b 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -89,6 +89,8 @@ MODULE_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_
+ EXTRA_DIST = \
+               pulse/client.conf.in \
+               pulse/version.h.in \
++              tizen-ivi-examples/audio-groups.conf \
++              tizen-ivi-examples/main-volume-policy.conf \
+               daemon/daemon.conf.in \
+               daemon/default.pa.in \
+               daemon/system.pa.in \
+@@ -111,6 +113,25 @@ pulseconf_DATA = \
+               daemon.conf \
+               client.conf
++# Add some Tizen specific configuration files.
++#
++# FIXME: These configuration files should be installed only if explicitly
++# requested, because they define policy which may not be the desired policy in
++# every Tizen profile. Currently default.pa loads module-audio-groups and
++# module-main-volume-policy only if module-murphy-ivi is installed in the
++# system, which helps with this issue, because non-IVI profiles don't
++# currently use module-murphy-ivi, so these configuration files won't have any
++# effect outside the IVI profile, but this is pretty hacky solution. It would
++# be better to load module-audio-groups and module-main-volume-policy
++# unconditionally, since they're not really tied to the Murphy module in any
++# way. We use this hack, because otherwise we'd need a configure switch for
++# enabling the example IVI configuration, and a new configure switch would also
++# require a new switch in the Tizen IVI image configuration. That's an extra
++# hurdle that we decided to avoid for now.
++pulseconf_DATA += \
++              tizen-ivi-examples/audio-groups.conf \
++              tizen-ivi-examples/main-volume-policy.conf
++
+ if HAVE_DBUS
+ dbuspolicy_DATA = \
+               daemon/pulseaudio-system.conf
+diff --git a/src/tizen-ivi-examples/audio-groups.conf b/src/tizen-ivi-examples/audio-groups.conf
+new file mode 100644
+index 0000000..54939c9
+--- /dev/null
++++ b/src/tizen-ivi-examples/audio-groups.conf
+@@ -0,0 +1,33 @@
++[General]
++audio-groups = x-example-call-downlink-audio-group x-example-navigator-output-audio-group x-example-default-output-audio-group
++streams = call-downlink navigator-output default-output
++
++[AudioGroup x-example-call-downlink-audio-group]
++description = Call downlink
++volume-control = create
++mute-control = create
++
++[AudioGroup x-example-navigator-output-audio-group]
++description = Navigator
++volume-control = create
++mute-control = create
++
++[AudioGroup x-example-default-output-audio-group]
++description = Default
++volume-control = create
++mute-control = create
++
++[Stream call-downlink]
++match = (direction output AND property media.role=phone)
++audio-group-for-volume = x-example-call-downlink-audio-group
++audio-group-for-mute = x-example-call-downlink-audio-group
++
++[Stream navigator-output]
++match = (direction output AND property media.role=navigator)
++audio-group-for-volume = x-example-navigator-output-audio-group
++audio-group-for-mute = x-example-navigator-output-audio-group
++
++[Stream default-output]
++match = (direction output)
++audio-group-for-volume = x-example-default-output-audio-group
++audio-group-for-mute = x-example-default-output-audio-group
+diff --git a/src/tizen-ivi-examples/main-volume-policy.conf b/src/tizen-ivi-examples/main-volume-policy.conf
+new file mode 100644
+index 0000000..5a73308
+--- /dev/null
++++ b/src/tizen-ivi-examples/main-volume-policy.conf
+@@ -0,0 +1,20 @@
++[General]
++output-volume-model = by-active-main-volume-context
++input-volume-model = none
++output-mute-model = by-active-main-volume-context
++input-mute-model = none
++main-volume-contexts = x-example-call-main-volume-context x-example-default-main-volume-context
++
++[MainVolumeContext x-example-call-main-volume-context]
++description = Call main volume context
++main-output-volume-control = bind:AudioGroup:x-example-call-downlink-audio-group
++main-input-volume-control = none
++main-output-mute-control = bind:AudioGroup:x-example-call-downlink-audio-group
++main-input-mute-control = none
++
++[MainVolumeContext x-example-default-main-volume-context]
++description = Default main volume context
++main-output-volume-control = bind:AudioGroup:x-example-default-output-audio-group
++main-input-volume-control = none
++main-output-mute-control = bind:AudioGroup:x-example-default-output-audio-group
++main-input-mute-control = none
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0084-sink-source-Initialize-port-before-fixate-hook-fixes.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0084-sink-source-Initialize-port-before-fixate-hook-fixes.patch
new file mode 100644 (file)
index 0000000..37b4f85
--- /dev/null
@@ -0,0 +1,159 @@
+From: David Henningsson <david.henningsson@canonical.com>
+Date: Fri, 21 Mar 2014 10:19:19 +0100
+Subject: sink/source: Initialize port before fixate hook (fixes volume/mute
+ not saved)
+
+In case a port has not yet been saved, which is e g often the case
+if a sink/source has only one port, reading volume/mute will be done
+without port, whereas writing volume/mute will be done with port.
+
+Work around this by setting a default port before the fixate hook,
+so module-device-restore can read volume/mute for the correct port.
+
+Change-Id: Iea6a742f0667771712059cb39b8082785b2a6887
+BugLink: https://bugs.launchpad.net/bugs/1289515
+Signed-off-by: David Henningsson <david.henningsson@canonical.com>
+---
+ src/pulsecore/device-port.c | 27 +++++++++++++++++++++++++++
+ src/pulsecore/device-port.h |  2 ++
+ src/pulsecore/sink.c        | 27 ++++++++++-----------------
+ src/pulsecore/source.c      | 28 ++++++++++------------------
+ 4 files changed, 49 insertions(+), 35 deletions(-)
+
+diff --git a/src/pulsecore/device-port.c b/src/pulsecore/device-port.c
+index c183990..1520142 100644
+--- a/src/pulsecore/device-port.c
++++ b/src/pulsecore/device-port.c
+@@ -191,3 +191,30 @@ void pa_device_port_active_changed(pa_device_port *port, bool new_active) {
+     pa_log_debug("Port %s %s.", port->name, new_active ? "activated" : "deactivated");
+     pa_hook_fire(&port->core->hooks[PA_CORE_HOOK_PORT_ACTIVE_CHANGED], port);
+ }
++
++pa_device_port *pa_device_port_find_best(pa_hashmap *ports)
++{
++    void *state;
++    pa_device_port *p, *best = NULL;
++
++    if (!ports)
++        return NULL;
++
++    /* First run: skip unavailable ports */
++    PA_HASHMAP_FOREACH(p, ports, state) {
++        if (p->available == PA_AVAILABLE_NO)
++            continue;
++
++        if (!best || p->priority > best->priority)
++            best = p;
++    }
++
++    /* Second run: if only unavailable ports exist, still suggest a port */
++    if (!best) {
++        PA_HASHMAP_FOREACH(p, ports, state)
++            if (!best || p->priority > best->priority)
++                best = p;
++    }
++
++    return best;
++}
+diff --git a/src/pulsecore/device-port.h b/src/pulsecore/device-port.h
+index 2964900..bf45ab9 100644
+--- a/src/pulsecore/device-port.h
++++ b/src/pulsecore/device-port.h
+@@ -87,4 +87,6 @@ void pa_device_port_set_latency_offset(pa_device_port *p, int64_t offset);
+ /* Called from sink.c and source.c only. */
+ void pa_device_port_active_changed(pa_device_port *port, bool new_active);
++pa_device_port *pa_device_port_find_best(pa_hashmap *ports);
++
+ #endif
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index 191b560..41cffcc 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -235,6 +235,12 @@ pa_sink* pa_sink_new(
+     pa_device_init_icon(data->proplist, true);
+     pa_device_init_intended_roles(data->proplist);
++    if (!data->active_port) {
++        pa_device_port *p = pa_device_port_find_best(data->ports);
++        if (p)
++            pa_sink_new_data_set_port(data, p->name);
++    }
++
+     if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_FIXATE], data) < 0) {
+         pa_xfree(s);
+         pa_namereg_unregister(core, name);
+@@ -300,23 +306,10 @@ pa_sink* pa_sink_new(
+         if ((s->active_port = pa_hashmap_get(s->ports, data->active_port)))
+             s->save_port = data->save_port;
+-    if (!s->active_port) {
+-        void *state;
+-        pa_device_port *p;
+-
+-        PA_HASHMAP_FOREACH(p, s->ports, state) {
+-            if (p->available == PA_AVAILABLE_NO)
+-                continue;
+-
+-            if (!s->active_port || p->priority > s->active_port->priority)
+-                s->active_port = p;
+-        }
+-        if (!s->active_port) {
+-            PA_HASHMAP_FOREACH(p, s->ports, state)
+-                if (!s->active_port || p->priority > s->active_port->priority)
+-                    s->active_port = p;
+-        }
+-    }
++    /* Hopefully the active port has already been assigned in the previous call
++       to pa_device_port_find_best, but better safe than sorry */
++    if (!s->active_port)
++        s->active_port = pa_device_port_find_best(s->ports);
+     if (s->active_port)
+         s->latency_offset = s->active_port->latency_offset;
+diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
+index 0fddfaa..db7f667 100644
+--- a/src/pulsecore/source.c
++++ b/src/pulsecore/source.c
+@@ -222,6 +222,12 @@ pa_source* pa_source_new(
+     pa_device_init_icon(data->proplist, false);
+     pa_device_init_intended_roles(data->proplist);
++    if (!data->active_port) {
++        pa_device_port *p = pa_device_port_find_best(data->ports);
++        if (p)
++            pa_source_new_data_set_port(data, p->name);
++    }
++
+     if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], data) < 0) {
+         pa_xfree(s);
+         pa_namereg_unregister(core, name);
+@@ -288,24 +294,10 @@ pa_source* pa_source_new(
+         if ((s->active_port = pa_hashmap_get(s->ports, data->active_port)))
+             s->save_port = data->save_port;
+-    if (!s->active_port) {
+-        void *state;
+-        pa_device_port *p;
+-
+-        PA_HASHMAP_FOREACH(p, s->ports, state) {
+-            if (p->available == PA_AVAILABLE_NO)
+-                continue;
+-
+-            if (!s->active_port || p->priority > s->active_port->priority)
+-                s->active_port = p;
+-        }
+-
+-        if (!s->active_port) {
+-            PA_HASHMAP_FOREACH(p, s->ports, state)
+-                if (!s->active_port || p->priority > s->active_port->priority)
+-                    s->active_port = p;
+-        }
+-    }
++    /* Hopefully the active port has already been assigned in the previous call
++       to pa_device_port_find_best, but better safe than sorry */
++    if (!s->active_port)
++        s->active_port = pa_device_port_find_best(s->ports);
+     if (s->active_port)
+         s->latency_offset = s->active_port->latency_offset;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0085-configuration-pulseaudio-tizen-configuration-in-defa.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0085-configuration-pulseaudio-tizen-configuration-in-defa.patch
new file mode 100644 (file)
index 0000000..8a27990
--- /dev/null
@@ -0,0 +1,123 @@
+From: Jaska Uimonen <jaska.uimonen@intel.com>
+Date: Thu, 18 Jul 2013 18:43:14 +0800
+Subject: configuration: pulseaudio tizen configuration in default.pa
+
+Change-Id: Id9370a1858d5c1ba0ed3319d717bc7f3e9ed5d31
+Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
+---
+ src/daemon/default.pa.in | 60 ++++++++++++++++++++++++------------------------
+ 1 file changed, 30 insertions(+), 30 deletions(-)
+
+diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in
+index f70804c..bae1a77 100755
+--- a/src/daemon/default.pa.in
++++ b/src/daemon/default.pa.in
+@@ -35,18 +35,6 @@ load-sample-dir-lazy %WINDIR%\Media\*.wav
+ .fail
+-### Automatically restore the volume of streams and devices
+-load-module module-device-restore
+-load-module module-stream-restore
+-load-module module-card-restore
+-
+-### Automatically augment property information from .desktop files
+-### stored in /usr/share/application
+-load-module module-augment-properties
+-
+-### Should be after module-*-restore but before module-*-detect
+-load-module module-switch-on-port-available
+-
+ ### Load audio drivers statically
+ ### (it's probably better to not load these drivers manually, but instead
+ ### use module-udev-detect -- see below -- for doing this automatically)
+@@ -81,16 +69,12 @@ load-module module-detect
+ ### Automatically connect sink and source if JACK server is present
+ .ifexists module-jackdbus-detect@PA_SOEXT@
+ .nofail
+-load-module module-jackdbus-detect channels=2
++load-module module-jackdbus-detect
+ .fail
+ .endif
+ ifelse(@HAVE_BLUEZ@, 1, [dnl
+ ### Automatically load driver modules for Bluetooth hardware
+-.ifexists module-bluetooth-policy@PA_SOEXT@
+-load-module module-bluetooth-policy
+-.endif
+-
+ .ifexists module-bluetooth-discover@PA_SOEXT@
+ load-module module-bluetooth-discover
+ .endif
+@@ -142,9 +126,6 @@ load-module module-rescue-streams
+ ### Make sure we always have a sink around, even if it is a null sink.
+ load-module module-always-sink
+-### Honour intended role device property
+-load-module module-intended-roles
+-
+ ### Automatically suspend sinks/sources that become idle for too long
+ load-module module-suspend-on-idle
+@@ -160,15 +141,16 @@ load-module module-systemd-login
+ ### Enable positioned event sounds
+ load-module module-position-event-sounds
+-### Cork music/video streams when a phone stream is active
+-load-module module-role-cork
+-
+ ### Modules to allow autoloading of filters (such as echo cancellation)
+ ### on demand. module-filter-heuristics tries to determine what filters
+ ### make sense, and module-filter-apply does the heavy-lifting of
+ ### loading modules and rerouting streams.
++.ifexists module-filter-heuristics@PA_SOEXT@
+ load-module module-filter-heuristics
++.endif
++.ifexists module-filter-apply@PA_SOEXT@
+ load-module module-filter-apply
++.endif
+ ])dnl
+ ifelse(@HAVE_X11@, 1, [dnl
+@@ -189,16 +171,34 @@ ifelse(@HAVE_X11@, 1, [dnl
+ #.endif
+ ])dnl
+-.ifexists module-volume-api
++### Load the Murphy IVI module if it exists
++.ifexists module-murphy-ivi@PA_SOEXT@
++load-module module-murphy-ivi
++load-module module-stream-restore restore_device=false on_hotplug=false on_rescue=false restore_volume=true preferred_stream_group=media.role.within.application.name
++load-module module-native-protocol-tcp
+ load-module module-volume-api
+-.endif
+-
+-.ifexists module-audio-groups
+ load-module module-audio-groups
+-.endif
+-
+-.ifexists module-main-volume-policy
+ load-module module-main-volume-policy
++.else
++### Automatically restore the volume of streams and devices
++load-module module-device-restore
++load-module module-stream-restore
++load-module module-card-restore
++
++### Automatically augment property information from .desktop files
++### stored in /usr/share/application
++.nofail
++load-module module-augment-properties
++.fail
++
++### Honour intended role device property
++load-module module-intended-roles
++
++### Cork music/video streams when a phone stream is active
++load-module module-role-cork
++
++load-module module-switch-on-port-available
++
+ .endif
+ ### Make some devices default
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0086-pactl-Fix-crash-in-pactl-list.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0086-pactl-Fix-crash-in-pactl-list.patch
new file mode 100644 (file)
index 0000000..e817684
--- /dev/null
@@ -0,0 +1,26 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 26 May 2014 16:26:14 +0300
+Subject: pactl: Fix crash in "pactl list"
+
+Fixes this assertion error:
+
+Assertion 'actions > 0' failed at utils/pactl.c:172, function complete_action(). Aborting.
+
+Change-Id: Icdcdf0817af431115444cb4fdef0a042fe5d7560
+---
+ src/utils/pactl.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/utils/pactl.c b/src/utils/pactl.c
+index f947681..d8921f1 100644
+--- a/src/utils/pactl.c
++++ b/src/utils/pactl.c
+@@ -1673,7 +1673,7 @@ static void volume_api_state_cb(pa_context *c, void *userdata) {
+                         o = pa_ext_volume_api_get_audio_group_info_list(c, get_audio_group_info_callback, NULL);
+                         pa_operation_unref(o);
+                         o = NULL;
+-                        actions += 4;
++                        actions += 5;
+                     } else if (pa_streq(list_type, "volume-controls")) {
+                         o = pa_ext_volume_api_get_volume_control_info_list(c, get_volume_control_info_callback, NULL);
+                         actions++;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0087-configuration-x-example-x-tizen-ivi-in-volume-config.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0087-configuration-x-example-x-tizen-ivi-in-volume-config.patch
new file mode 100644 (file)
index 0000000..2882e56
--- /dev/null
@@ -0,0 +1,173 @@
+From: Ismo Puustinen <ismo.puustinen@intel.com>
+Date: Mon, 26 May 2014 14:37:48 +0300
+Subject: configuration: x-example -> x-tizen-ivi in volume configuration.
+
+Change-Id: I1c11084d6891e431dd909c632e4bfb62968167df
+---
+ src/Makefile.am                                |  8 +++----
+ src/tizen-ivi-examples/audio-groups.conf       | 33 --------------------------
+ src/tizen-ivi-examples/main-volume-policy.conf | 20 ----------------
+ src/tizen-ivi/audio-groups.conf                | 33 ++++++++++++++++++++++++++
+ src/tizen-ivi/main-volume-policy.conf          | 20 ++++++++++++++++
+ 5 files changed, 57 insertions(+), 57 deletions(-)
+ delete mode 100644 src/tizen-ivi-examples/audio-groups.conf
+ delete mode 100644 src/tizen-ivi-examples/main-volume-policy.conf
+ create mode 100644 src/tizen-ivi/audio-groups.conf
+ create mode 100644 src/tizen-ivi/main-volume-policy.conf
+
+diff --git a/src/Makefile.am b/src/Makefile.am
+index d57c30b..9d17336 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -89,8 +89,8 @@ MODULE_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_
+ EXTRA_DIST = \
+               pulse/client.conf.in \
+               pulse/version.h.in \
+-              tizen-ivi-examples/audio-groups.conf \
+-              tizen-ivi-examples/main-volume-policy.conf \
++              tizen-ivi/audio-groups.conf \
++              tizen-ivi/main-volume-policy.conf \
+               daemon/daemon.conf.in \
+               daemon/default.pa.in \
+               daemon/system.pa.in \
+@@ -129,8 +129,8 @@ pulseconf_DATA = \
+ # require a new switch in the Tizen IVI image configuration. That's an extra
+ # hurdle that we decided to avoid for now.
+ pulseconf_DATA += \
+-              tizen-ivi-examples/audio-groups.conf \
+-              tizen-ivi-examples/main-volume-policy.conf
++              tizen-ivi/audio-groups.conf \
++              tizen-ivi/main-volume-policy.conf
+ if HAVE_DBUS
+ dbuspolicy_DATA = \
+diff --git a/src/tizen-ivi-examples/audio-groups.conf b/src/tizen-ivi-examples/audio-groups.conf
+deleted file mode 100644
+index 54939c9..0000000
+--- a/src/tizen-ivi-examples/audio-groups.conf
++++ /dev/null
+@@ -1,33 +0,0 @@
+-[General]
+-audio-groups = x-example-call-downlink-audio-group x-example-navigator-output-audio-group x-example-default-output-audio-group
+-streams = call-downlink navigator-output default-output
+-
+-[AudioGroup x-example-call-downlink-audio-group]
+-description = Call downlink
+-volume-control = create
+-mute-control = create
+-
+-[AudioGroup x-example-navigator-output-audio-group]
+-description = Navigator
+-volume-control = create
+-mute-control = create
+-
+-[AudioGroup x-example-default-output-audio-group]
+-description = Default
+-volume-control = create
+-mute-control = create
+-
+-[Stream call-downlink]
+-match = (direction output AND property media.role=phone)
+-audio-group-for-volume = x-example-call-downlink-audio-group
+-audio-group-for-mute = x-example-call-downlink-audio-group
+-
+-[Stream navigator-output]
+-match = (direction output AND property media.role=navigator)
+-audio-group-for-volume = x-example-navigator-output-audio-group
+-audio-group-for-mute = x-example-navigator-output-audio-group
+-
+-[Stream default-output]
+-match = (direction output)
+-audio-group-for-volume = x-example-default-output-audio-group
+-audio-group-for-mute = x-example-default-output-audio-group
+diff --git a/src/tizen-ivi-examples/main-volume-policy.conf b/src/tizen-ivi-examples/main-volume-policy.conf
+deleted file mode 100644
+index 5a73308..0000000
+--- a/src/tizen-ivi-examples/main-volume-policy.conf
++++ /dev/null
+@@ -1,20 +0,0 @@
+-[General]
+-output-volume-model = by-active-main-volume-context
+-input-volume-model = none
+-output-mute-model = by-active-main-volume-context
+-input-mute-model = none
+-main-volume-contexts = x-example-call-main-volume-context x-example-default-main-volume-context
+-
+-[MainVolumeContext x-example-call-main-volume-context]
+-description = Call main volume context
+-main-output-volume-control = bind:AudioGroup:x-example-call-downlink-audio-group
+-main-input-volume-control = none
+-main-output-mute-control = bind:AudioGroup:x-example-call-downlink-audio-group
+-main-input-mute-control = none
+-
+-[MainVolumeContext x-example-default-main-volume-context]
+-description = Default main volume context
+-main-output-volume-control = bind:AudioGroup:x-example-default-output-audio-group
+-main-input-volume-control = none
+-main-output-mute-control = bind:AudioGroup:x-example-default-output-audio-group
+-main-input-mute-control = none
+diff --git a/src/tizen-ivi/audio-groups.conf b/src/tizen-ivi/audio-groups.conf
+new file mode 100644
+index 0000000..4839307
+--- /dev/null
++++ b/src/tizen-ivi/audio-groups.conf
+@@ -0,0 +1,33 @@
++[General]
++audio-groups = x-tizen-ivi-call-downlink-audio-group x-tizen-ivi-navigator-output-audio-group x-tizen-ivi-default-output-audio-group
++streams = call-downlink navigator-output default-output
++
++[AudioGroup x-tizen-ivi-call-downlink-audio-group]
++description = Call downlink
++volume-control = create
++mute-control = create
++
++[AudioGroup x-tizen-ivi-navigator-output-audio-group]
++description = Navigator
++volume-control = create
++mute-control = create
++
++[AudioGroup x-tizen-ivi-default-output-audio-group]
++description = Default
++volume-control = create
++mute-control = create
++
++[Stream call-downlink]
++match = (direction output AND property media.role=phone)
++audio-group-for-volume = x-tizen-ivi-call-downlink-audio-group
++audio-group-for-mute = x-tizen-ivi-call-downlink-audio-group
++
++[Stream navigator-output]
++match = (direction output AND property media.role=navigator)
++audio-group-for-volume = x-tizen-ivi-navigator-output-audio-group
++audio-group-for-mute = x-tizen-ivi-navigator-output-audio-group
++
++[Stream default-output]
++match = (direction output)
++audio-group-for-volume = x-tizen-ivi-default-output-audio-group
++audio-group-for-mute = x-tizen-ivi-default-output-audio-group
+diff --git a/src/tizen-ivi/main-volume-policy.conf b/src/tizen-ivi/main-volume-policy.conf
+new file mode 100644
+index 0000000..0a83968
+--- /dev/null
++++ b/src/tizen-ivi/main-volume-policy.conf
+@@ -0,0 +1,20 @@
++[General]
++output-volume-model = by-active-main-volume-context
++input-volume-model = none
++output-mute-model = by-active-main-volume-context
++input-mute-model = none
++main-volume-contexts = x-tizen-ivi-call default
++
++[MainVolumeContext x-tizen-ivi-call]
++description = Call main volume context
++main-output-volume-control = bind:AudioGroup:x-tizen-ivi-call-downlink-audio-group
++main-input-volume-control = none
++main-output-mute-control = bind:AudioGroup:x-tizen-ivi-call-downlink-audio-group
++main-input-mute-control = none
++
++[MainVolumeContext default]
++description = Default main volume context
++main-output-volume-control = bind:AudioGroup:x-tizen-ivi-default-output-audio-group
++main-input-volume-control = none
++main-output-mute-control = bind:AudioGroup:x-tizen-ivi-default-output-audio-group
++main-input-mute-control = none
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0088-device-creator-stream-creator-Add-a-couple-of-assert.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0088-device-creator-stream-creator-Add-a-couple-of-assert.patch
new file mode 100644 (file)
index 0000000..a637c29
--- /dev/null
@@ -0,0 +1,47 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Tue, 27 May 2014 10:45:04 +0300
+Subject: device-creator, stream-creator: Add a couple of assertions
+
+Klocwork complained that source (in device-creator) and output (in
+stream-creator) may be dereferenced if they're NULL. Let's make
+Klocwork happy, and the code a bit more obvious to human readers as
+well.
+
+Change-Id: I835dd7d9da44e2866a97bc0424001a42c29602a8
+---
+ src/modules/volume-api/device-creator.c | 4 +++-
+ src/modules/volume-api/stream-creator.c | 4 +++-
+ 2 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/src/modules/volume-api/device-creator.c b/src/modules/volume-api/device-creator.c
+index 1d912ba..f35fab0 100644
+--- a/src/modules/volume-api/device-creator.c
++++ b/src/modules/volume-api/device-creator.c
+@@ -513,8 +513,10 @@ static pa_hook_result_t sink_or_source_mute_changed_cb(void *hook_data, void *ca
+     if (sink)
+         mute = sink->muted;
+-    else
++    else if (source)
+         mute = source->muted;
++    else
++        pa_assert_not_reached();
+     pa_mute_control_mute_changed(control->mute_control, mute);
+diff --git a/src/modules/volume-api/stream-creator.c b/src/modules/volume-api/stream-creator.c
+index 2bd0053..f6ca7b3 100644
+--- a/src/modules/volume-api/stream-creator.c
++++ b/src/modules/volume-api/stream-creator.c
+@@ -216,8 +216,10 @@ static pa_hook_result_t sink_input_or_source_output_mute_changed_cb(void *hook_d
+     if (input)
+         mute = input->muted;
+-    else
++    else if (output)
+         mute = output->muted;
++    else
++        pa_assert_not_reached();
+     pa_mute_control_mute_changed(stream->stream->own_mute_control, mute);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0089-main-volume-policy-Fix-a-memory-leak.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0089-main-volume-policy-Fix-a-memory-leak.patch
new file mode 100644 (file)
index 0000000..8c46a1b
--- /dev/null
@@ -0,0 +1,64 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Tue, 27 May 2014 11:06:10 +0300
+Subject: main-volume-policy: Fix a memory leak
+
+There can be multiple calls to pa_binding_target_info_new() in this
+function, but only the last allocated info object was freed.
+
+Change-Id: I9df43f0663b27b07ba7b8d01bc8ea9cc0a6c1b51
+---
+ src/modules/main-volume-policy/module-main-volume-policy.c | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+diff --git a/src/modules/main-volume-policy/module-main-volume-policy.c b/src/modules/main-volume-policy/module-main-volume-policy.c
+index a14699d..0a89aa7 100644
+--- a/src/modules/main-volume-policy/module-main-volume-policy.c
++++ b/src/modules/main-volume-policy/module-main-volume-policy.c
+@@ -253,7 +253,7 @@ static pa_hook_result_t active_main_volume_context_changed_cb(void *hook_data, v
+     struct userdata *u = userdata;
+     pa_main_volume_context *context;
+     pa_volume_api *api;
+-    pa_binding_target_info *info = NULL;
++    pa_binding_target_info *info;
+     pa_assert(u);
+@@ -265,6 +265,7 @@ static pa_hook_result_t active_main_volume_context_changed_cb(void *hook_data, v
+             info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
+                                               PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL);
+             pa_volume_api_bind_main_output_volume_control(api, info);
++            pa_binding_target_info_free(info);
+         } else
+             pa_volume_api_set_main_output_volume_control(api, NULL);
+     }
+@@ -274,6 +275,7 @@ static pa_hook_result_t active_main_volume_context_changed_cb(void *hook_data, v
+             info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
+                                               PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL);
+             pa_volume_api_bind_main_input_volume_control(api, info);
++            pa_binding_target_info_free(info);
+         } else
+             pa_volume_api_set_main_input_volume_control(api, NULL);
+     }
+@@ -283,6 +285,7 @@ static pa_hook_result_t active_main_volume_context_changed_cb(void *hook_data, v
+             info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
+                                               PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL);
+             pa_volume_api_bind_main_output_mute_control(api, info);
++            pa_binding_target_info_free(info);
+         } else
+             pa_volume_api_set_main_output_mute_control(api, NULL);
+     }
+@@ -292,13 +295,11 @@ static pa_hook_result_t active_main_volume_context_changed_cb(void *hook_data, v
+             info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
+                                               PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL);
+             pa_volume_api_bind_main_input_mute_control(api, info);
++            pa_binding_target_info_free(info);
+         } else
+             pa_volume_api_set_main_input_mute_control(api, NULL);
+     }
+-    if (info)
+-        pa_binding_target_info_free(info);
+-
+     return PA_HOOK_OK;
+ }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0090-audio-groups-fix-issues-found-by-static-analysis.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0090-audio-groups-fix-issues-found-by-static-analysis.patch
new file mode 100644 (file)
index 0000000..52bfd20
--- /dev/null
@@ -0,0 +1,146 @@
+From: Ismo Puustinen <ismo.puustinen@intel.com>
+Date: Tue, 27 May 2014 10:27:16 +0300
+Subject: audio-groups: fix issues found by static analysis.
+
+Change-Id: Ia2805a5977868b236bd6a33e7bc8fdcb944020ea
+---
+ src/modules/audio-groups/module-audio-groups.c | 63 ++++++++++++++++----------
+ 1 file changed, 40 insertions(+), 23 deletions(-)
+
+diff --git a/src/modules/audio-groups/module-audio-groups.c b/src/modules/audio-groups/module-audio-groups.c
+index 320847c..2b3a570 100644
+--- a/src/modules/audio-groups/module-audio-groups.c
++++ b/src/modules/audio-groups/module-audio-groups.c
+@@ -383,9 +383,13 @@ static bool match_predicate(struct literal *l, pas_stream *d) {
+     else if (l->property_name && l->property_value) {
+         /* check the property from the property list */
+-        if (pa_proplist_contains(d->proplist, l->property_name) &&
+-                strcmp(pa_proplist_gets(d->proplist, l->property_name), l->property_value) == 0)
+-            return true;
++        if (pa_proplist_contains(d->proplist, l->property_name)) {
++            const char *prop = pa_proplist_gets(d->proplist, l->property_name);
++
++            if (prop && strcmp(prop, l->property_value) == 0) {
++                return true;
++            }
++        }
+     }
+     /* no match */
+@@ -631,18 +635,8 @@ static struct expression_token *parse_rule_internal(const char *rule, bool disju
+     char *p;
+     int brace_count = 0;
+     bool braces_present = false;
+-    char left_buf[len];
+-    char right_buf[len];
+-
+-#if 0
+-    /* check if the rule is still valid */
+-
+-    if (len < 2)
+-        return NULL;
+-
+-    if (rule[0] != '(' || rule[len-1] != ')')
+-        return NULL;
+-#endif
++    char left_buf[len+1];
++    char right_buf[len+1];
+     et = pa_xnew0(struct expression_token, 1);
+@@ -740,9 +734,9 @@ static struct expression_token *parse_rule_internal(const char *rule, bool disju
+     else {
+         /* this is a literal */
+         char *begin_lit;
+-        char buf[strlen(rule)+1];
+-
++        char buf[len+1];
+         struct literal_token *lit = pa_xnew0(struct literal_token, 1);
++
+         if (!lit) {
+             delete_expression_token(et);
+             return NULL;
+@@ -769,7 +763,8 @@ static struct expression_token *parse_rule_internal(const char *rule, bool disju
+             *l = '\0';
+         }
+         else {
+-            strncpy(buf, rule, sizeof(buf));
++            strncpy(buf, rule, len);
++            buf[len] = '\0';
+         }
+         if (strncmp(buf, "NEG", 3) == 0) {
+@@ -782,6 +777,7 @@ static struct expression_token *parse_rule_internal(const char *rule, bool disju
+         }
+         lit->var = pa_xstrdup(begin_lit);
++
+         et->lit = lit;
+     }
+@@ -869,8 +865,9 @@ static bool gather_conjunction(struct expression_token *et, struct conjunction *
+     if (et->oper == operator_and) {
+         if (!gather_conjunction(et->left, c) ||
+-            !gather_conjunction(et->right, c))
++            !gather_conjunction(et->right, c)) {
+             return false;
++        }
+     }
+     else {
+         /* literal */
+@@ -879,8 +876,13 @@ static bool gather_conjunction(struct expression_token *et, struct conjunction *
+         if (!l)
+             return false;
+-        gather_literal(et, l);
++        if (!gather_literal(et, l)) {
++            pa_log_error("audio groups config: literal parsing failed");
++            delete_literal(l);
++            return false;
++        }
++        PA_LLIST_INIT(struct literal, l);
+         PA_LLIST_PREPEND(struct literal, c->literals, l);
+     }
+@@ -897,9 +899,18 @@ static bool gather_expression(struct expression *e, struct expression_token *et)
+     else {
+         /* conjunction or literal */
+         struct conjunction *c = pa_xnew0(struct conjunction, 1);
+-        if (!gather_conjunction(et, c))
++
++        if (!c)
+             return false;
++        PA_LLIST_HEAD_INIT(struct literal, c->literals);
++
++        if (!gather_conjunction(et, c)) {
++            delete_conjunction(c);
++            return false;
++        }
++
++        PA_LLIST_INIT(struct conjunction, c);
+         PA_LLIST_PREPEND(struct conjunction, e->conjunctions, c);
+     }
+@@ -950,10 +961,16 @@ static struct expression *parse_rule(const char *rule_string) {
+     if (!e)
+         goto error;
++    PA_LLIST_HEAD_INIT(struct conjunction, e->conjunctions);
++
+     /* gather expressions to actual match format */
+-    gather_expression(e, et);
++    if (!gather_expression(e, et)) {
++        /* gathering the expression from tokens went wrong */
++        pa_log_error("failed to parse audio group stream classification data");
++        goto error;
++    }
+-#if 1
++#if 0
+     print_expression(e);
+ #endif
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0091-core-util-Add-pa_append_to_home_dir.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0091-core-util-Add-pa_append_to_home_dir.patch
new file mode 100644 (file)
index 0000000..89732c8
--- /dev/null
@@ -0,0 +1,72 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Sun, 8 Jun 2014 16:32:57 +0300
+Subject: core-util: Add pa_append_to_home_dir()
+
+Change-Id: I746d2efb5f205820480b0cbd11c23cff11367656
+---
+ src/pulsecore/authkey.c   |  7 ++-----
+ src/pulsecore/core-util.c | 17 +++++++++++++++++
+ src/pulsecore/core-util.h |  1 +
+ 3 files changed, 20 insertions(+), 5 deletions(-)
+
+diff --git a/src/pulsecore/authkey.c b/src/pulsecore/authkey.c
+index 03c0c4b..be31e98 100644
+--- a/src/pulsecore/authkey.c
++++ b/src/pulsecore/authkey.c
+@@ -156,14 +156,11 @@ static char *normalize_path(const char *fn) {
+ #else
+     if (strlen(fn) < 3 || !IsCharAlpha(fn[0]) || fn[1] != ':' || fn[2] != '\\') {
+ #endif
+-        char *homedir, *s;
++        char *s;
+-        if (!(homedir = pa_get_home_dir_malloc()))
++        if (pa_append_to_home_dir(fn, &s) < 0)
+             return NULL;
+-        s = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", homedir, fn);
+-        pa_xfree(homedir);
+-
+         return s;
+     }
+diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c
+index 508b3d3..99aa51f 100644
+--- a/src/pulsecore/core-util.c
++++ b/src/pulsecore/core-util.c
+@@ -1685,6 +1685,23 @@ char *pa_get_home_dir_malloc(void) {
+     return homedir;
+ }
++int pa_append_to_home_dir(const char *path, char **_r) {
++    char *home_dir;
++
++    pa_assert(path);
++    pa_assert(_r);
++
++    home_dir = pa_get_home_dir_malloc();
++    if (!home_dir) {
++        pa_log("Failed to get home directory.");
++        return -PA_ERR_NOENTITY;
++    }
++
++    *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", home_dir, path);
++    pa_xfree(home_dir);
++    return 0;
++}
++
+ char *pa_get_binary_name_malloc(void) {
+     char *t;
+     size_t allocated = 128;
+diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
+index aba1863..05d628e 100644
+--- a/src/pulsecore/core-util.h
++++ b/src/pulsecore/core-util.h
+@@ -137,6 +137,7 @@ char* pa_find_config_file(const char *global, const char *local, const char *env
+ char *pa_get_runtime_dir(void);
+ char *pa_get_state_dir(void);
+ char *pa_get_home_dir_malloc(void);
++int pa_append_to_home_dir(const char *path, char **_r);
+ char *pa_get_binary_name_malloc(void);
+ char *pa_runtime_path(const char *fn);
+ char *pa_state_path(const char *fn, bool prepend_machine_id);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0092-core-util-Add-pa_get_config_home_dir.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0092-core-util-Add-pa_get_config_home_dir.patch
new file mode 100644 (file)
index 0000000..e5e05e1
--- /dev/null
@@ -0,0 +1,116 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Sun, 8 Jun 2014 16:32:59 +0300
+Subject: core-util: Add pa_get_config_home_dir()
+
+Change-Id: I6aa3df386a7414563b03435683bad2596cf60b8b
+---
+ src/pulsecore/core-util.c | 58 ++++++++++++++++++++++++++++++++---------------
+ src/pulsecore/core-util.h |  1 +
+ 2 files changed, 41 insertions(+), 18 deletions(-)
+
+diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c
+index 99aa51f..2569cfd 100644
+--- a/src/pulsecore/core-util.c
++++ b/src/pulsecore/core-util.c
+@@ -1581,16 +1581,6 @@ int pa_unlock_lockfile(const char *fn, int fd) {
+     return r;
+ }
+-static char *get_config_home(char *home) {
+-    char *t;
+-
+-    t = getenv("XDG_CONFIG_HOME");
+-    if (t)
+-        return pa_xstrdup(t);
+-
+-    return pa_sprintf_malloc("%s" PA_PATH_SEP ".config", home);
+-}
+-
+ static int check_ours(const char *p) {
+     struct stat st;
+@@ -1608,7 +1598,7 @@ static int check_ours(const char *p) {
+ }
+ static char *get_pulse_home(void) {
+-    char *h, *ret, *config_home;
++    char *h, *ret;
+     int t;
+     h = pa_get_home_dir_malloc();
+@@ -1626,17 +1616,14 @@ static char *get_pulse_home(void) {
+     /* If the old directory exists, use it. */
+     ret = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse", h);
+-    if (access(ret, F_OK) >= 0) {
+-        free(h);
++    pa_xfree(h);
++    if (access(ret, F_OK) >= 0)
+         return ret;
+-    }
+     free(ret);
+     /* Otherwise go for the XDG compliant directory. */
+-    config_home = get_config_home(h);
+-    free(h);
+-    ret = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse", config_home);
+-    free(config_home);
++    if (pa_get_config_home_dir(false, &ret) < 0)
++        return NULL;
+     return ret;
+ }
+@@ -1702,6 +1689,41 @@ int pa_append_to_home_dir(const char *path, char **_r) {
+     return 0;
+ }
++int pa_get_config_home_dir(bool use_machine_id, char **_r) {
++    const char *e;
++    char *base = NULL;
++    int r = 0;
++    char *machine_id = NULL;
++
++    pa_assert(_r);
++
++    e = getenv("XDG_CONFIG_HOME");
++    if (e && *e)
++        base = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse", e);
++    else {
++        r = pa_append_to_home_dir(".config" PA_PATH_SEP "pulse", &base);
++        if (r < 0)
++            goto finish;
++    }
++
++    if (use_machine_id) {
++        machine_id = pa_machine_id();
++        if (!machine_id) {
++            r = -PA_ERR_NOENTITY;
++            goto finish;
++        }
++
++        *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", base, machine_id);
++    } else
++        *_r = pa_xstrdup(base);
++
++finish:
++    pa_xfree(machine_id);
++    pa_xfree(base);
++
++    return r;
++}
++
+ char *pa_get_binary_name_malloc(void) {
+     char *t;
+     size_t allocated = 128;
+diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
+index 05d628e..e222874 100644
+--- a/src/pulsecore/core-util.h
++++ b/src/pulsecore/core-util.h
+@@ -138,6 +138,7 @@ char *pa_get_runtime_dir(void);
+ char *pa_get_state_dir(void);
+ char *pa_get_home_dir_malloc(void);
+ int pa_append_to_home_dir(const char *path, char **_r);
++int pa_get_config_home_dir(bool use_machine_id, char **_r);
+ char *pa_get_binary_name_malloc(void);
+ char *pa_runtime_path(const char *fn);
+ char *pa_state_path(const char *fn, bool prepend_machine_id);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0093-core-util-Add-pa_append_to_config_home_dir.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0093-core-util-Add-pa_append_to_config_home_dir.patch
new file mode 100644 (file)
index 0000000..da7a6ef
--- /dev/null
@@ -0,0 +1,49 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Sun, 8 Jun 2014 16:33:00 +0300
+Subject: core-util: Add pa_append_to_config_home_dir()
+
+Change-Id: Ib4e6a096a740a61188220a983f26ecea434f6200
+---
+ src/pulsecore/core-util.c | 16 ++++++++++++++++
+ src/pulsecore/core-util.h |  1 +
+ 2 files changed, 17 insertions(+)
+
+diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c
+index 2569cfd..dbe9ec1 100644
+--- a/src/pulsecore/core-util.c
++++ b/src/pulsecore/core-util.c
+@@ -1724,6 +1724,22 @@ finish:
+     return r;
+ }
++int pa_append_to_config_home_dir(const char *path, bool use_machine_id, char **_r) {
++    int r;
++    char *config_home_dir;
++
++    pa_assert(path);
++    pa_assert(_r);
++
++    r = pa_get_config_home_dir(use_machine_id, &config_home_dir);
++    if (r < 0)
++        return r;
++
++    *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", config_home_dir, path);
++    pa_xfree(config_home_dir);
++    return 0;
++}
++
+ char *pa_get_binary_name_malloc(void) {
+     char *t;
+     size_t allocated = 128;
+diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
+index e222874..4968385 100644
+--- a/src/pulsecore/core-util.h
++++ b/src/pulsecore/core-util.h
+@@ -139,6 +139,7 @@ char *pa_get_state_dir(void);
+ char *pa_get_home_dir_malloc(void);
+ int pa_append_to_home_dir(const char *path, char **_r);
+ int pa_get_config_home_dir(bool use_machine_id, char **_r);
++int pa_append_to_config_home_dir(const char *path, bool use_machine_id, char **_r);
+ char *pa_get_binary_name_malloc(void);
+ char *pa_runtime_path(const char *fn);
+ char *pa_state_path(const char *fn, bool prepend_machine_id);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0094-core-Create-the-config-home-directory-on-startup.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0094-core-Create-the-config-home-directory-on-startup.patch
new file mode 100644 (file)
index 0000000..2d86fde
--- /dev/null
@@ -0,0 +1,55 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Tue, 17 Jun 2014 19:35:21 +0300
+Subject: core: Create the config home directory on startup
+
+This avoids the need to check for the existence of the config home
+directory every time some file needs to be opened from that directory.
+
+Change-Id: I449c61aa46eaea3f8c7eb0aa040310db58421828
+---
+ src/pulsecore/core.c | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
+index e6f2dfc..4a66f80 100644
+--- a/src/pulsecore/core.c
++++ b/src/pulsecore/core.c
+@@ -24,6 +24,7 @@
+ #include <config.h>
+ #endif
++#include <errno.h>
+ #include <stdlib.h>
+ #include <stdio.h>
+ #include <signal.h>
+@@ -33,6 +34,7 @@
+ #include <pulse/xmalloc.h>
+ #include <pulsecore/module.h>
++#include <pulsecore/core-error.h>
+ #include <pulsecore/core-rtclock.h>
+ #include <pulsecore/core-util.h>
+ #include <pulsecore/core-scache.h>
+@@ -64,12 +66,22 @@ static int core_process_msg(pa_msgobject *o, int code, void *userdata, int64_t o
+ static void core_free(pa_object *o);
+ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, size_t shm_size) {
++    int r;
++    char *config_home_dir;
+     pa_core* c;
+     pa_mempool *pool;
+     int j;
+     pa_assert(m);
++    r = pa_get_config_home_dir(true, &config_home_dir);
++    if (r >= 0) {
++        r = pa_make_secure_dir(config_home_dir, 0700, (uid_t) -1, (gid_t) -1, true);
++        pa_xfree(config_home_dir);
++        if (r < 0)
++            pa_log("Failed to create config home directory (%s): %s", config_home_dir, pa_cstrerror(errno));
++    }
++
+     if (shared) {
+         if (!(pool = pa_mempool_new(shared, shm_size))) {
+             pa_log_warn("failed to allocate shared memory pool. Falling back to a normal memory pool.");
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0095-alsa-Handle-unlinking-of-uninitialized-streams-grace.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0095-alsa-Handle-unlinking-of-uninitialized-streams-grace.patch
new file mode 100644 (file)
index 0000000..54b6530
--- /dev/null
@@ -0,0 +1,42 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 4 Aug 2014 15:33:01 +0300
+Subject: alsa: Handle unlinking of uninitialized streams gracefully
+
+There should be no assumptions about what has been initialized when
+the unlink hook is fired.
+
+Change-Id: I7502f0e7a3d244413dd806bc8657014999c9b9b3
+---
+ src/modules/alsa/module-alsa-card.c | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
+index 1e63230..7c408ee 100644
+--- a/src/modules/alsa/module-alsa-card.c
++++ b/src/modules/alsa/module-alsa-card.c
+@@ -579,7 +579,11 @@ static pa_hook_result_t sink_input_unlink_hook_callback(pa_core *c, pa_sink_inpu
+     const char *role;
+     pa_sink *sink = sink_input->sink;
+-    pa_assert(sink);
++    if (!sink)
++        return PA_HOOK_OK;
++
++    if (!sink_input->proplist)
++        return PA_HOOK_OK;
+     role = pa_proplist_gets(sink_input->proplist, PA_PROP_MEDIA_ROLE);
+@@ -594,7 +598,11 @@ static pa_hook_result_t source_output_unlink_hook_callback(pa_core *c, pa_source
+     const char *role;
+     pa_source *source = source_output->source;
+-    pa_assert(source);
++    if (!source)
++        return PA_HOOK_OK;
++
++    if (!source_output->proplist)
++        return PA_HOOK_OK;
+     role = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0096-role-cork-Handle-unlinking-of-uninitialized-streams-.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0096-role-cork-Handle-unlinking-of-uninitialized-streams-.patch
new file mode 100644 (file)
index 0000000..0bd8821
--- /dev/null
@@ -0,0 +1,26 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 4 Aug 2014 15:59:06 +0300
+Subject: role-cork: Handle unlinking of uninitialized streams gracefully
+
+There should be no assumptions about what has been initialized when
+the unlink hook is fired.
+
+Change-Id: I4ea4372570e7a0a83c31caab6e2e6781a98cd3ad
+---
+ src/modules/module-role-cork.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/modules/module-role-cork.c b/src/modules/module-role-cork.c
+index 8ca2109..d72350c 100644
+--- a/src/modules/module-role-cork.c
++++ b/src/modules/module-role-cork.c
+@@ -162,6 +162,9 @@ static pa_hook_result_t process(struct userdata *u, pa_sink_input *i, bool creat
+     if (!create)
+         pa_hashmap_remove(u->cork_state, i);
++    if (!i->proplist)
++        return PA_HOOK_OK;
++
+     if (!(role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)))
+         return PA_HOOK_OK;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0097-role-ducking-Handle-unlinking-of-uninitialized-strea.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0097-role-ducking-Handle-unlinking-of-uninitialized-strea.patch
new file mode 100644 (file)
index 0000000..0f3d20a
--- /dev/null
@@ -0,0 +1,26 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 4 Aug 2014 16:00:01 +0300
+Subject: role-ducking: Handle unlinking of uninitialized streams gracefully
+
+There should be no assumptions about what has been initialized when
+the unlink hook is fired.
+
+Change-Id: Id3c069093894bf508fbc0be75db046ff875ce965
+---
+ src/modules/module-role-ducking.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/modules/module-role-ducking.c b/src/modules/module-role-ducking.c
+index 9947871..d1ae357 100644
+--- a/src/modules/module-role-ducking.c
++++ b/src/modules/module-role-ducking.c
+@@ -159,6 +159,9 @@ static pa_hook_result_t process(struct userdata *u, pa_sink_input *i, bool duck)
+     pa_assert(u);
+     pa_sink_input_assert_ref(i);
++    if (!i->proplist)
++        return PA_HOOK_OK;
++
+     if (!(role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)))
+         return PA_HOOK_OK;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0098-core-util-Add-pa_boolean_to_string.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0098-core-util-Add-pa_boolean_to_string.patch
new file mode 100644 (file)
index 0000000..ad48a21
--- /dev/null
@@ -0,0 +1,28 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 4 Aug 2014 20:31:00 +0300
+Subject: core-util: Add pa_boolean_to_string()
+
+I need to save booleans in a plain text database, and the existing
+pa_yes_no() is not good, because it will do translation in the next
+PulseAudio release.
+
+Change-Id: I85f12da01aa0eb3d5c555350bd14ba337fbcc25b
+---
+ src/pulsecore/core-util.h | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
+index 4968385..c108968 100644
+--- a/src/pulsecore/core-util.h
++++ b/src/pulsecore/core-util.h
+@@ -89,6 +89,10 @@ int pa_parse_boolean(const char *s) PA_GCC_PURE;
+ int pa_parse_volume(const char *s, pa_volume_t *volume);
++static inline const char *pa_boolean_to_string(bool b) {
++    return b ? "true" : "false";
++}
++
+ static inline const char *pa_yes_no(bool b) {
+     return b ? "yes" : "no";
+ }
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0099-sink-input-source-output-Assign-to-reference_ratio-f.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0099-sink-input-source-output-Assign-to-reference_ratio-f.patch
new file mode 100644 (file)
index 0000000..d8c815a
--- /dev/null
@@ -0,0 +1,243 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 4 Aug 2014 20:57:55 +0300
+Subject: sink-input,
+ source-output: Assign to reference_ratio from a single place
+
+This makes it easy to log a message every time the reference ratio
+changes. I also need to add a hook for reference ratio changes, but
+that need will go away if the stream relative volume controls will be
+created by the core in the future.
+
+Change-Id: I2344ba7825f76cd72241599bd138b21e16555e01
+---
+ src/pulsecore/sink-input.c    | 28 ++++++++++++++++++++++++++--
+ src/pulsecore/sink-input.h    |  6 ++++++
+ src/pulsecore/sink.c          | 11 +++++++----
+ src/pulsecore/source-output.c | 26 +++++++++++++++++++++++++-
+ src/pulsecore/source-output.h |  6 ++++++
+ src/pulsecore/source.c        | 11 +++++++----
+ 6 files changed, 77 insertions(+), 11 deletions(-)
+
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index 9d13269..57c906d 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -1305,7 +1305,7 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool s
+         /* OK, we are in normal volume mode. The volume only affects
+          * ourselves */
+         set_real_ratio(i, volume);
+-        i->reference_ratio = i->volume;
++        pa_sink_input_set_reference_ratio(i, &i->volume);
+         /* Copy the new soft_volume to the thread_info struct */
+         pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0);
+@@ -1757,7 +1757,7 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
+             pa_cvolume_reset(&new_volume, i->volume.channels);
+             pa_sink_input_set_volume_direct(i, &new_volume);
+-            pa_cvolume_reset(&i->reference_ratio, i->reference_ratio.channels);
++            pa_sink_input_set_reference_ratio(i, &new_volume);
+             pa_assert(pa_cvolume_is_norm(&i->real_ratio));
+             pa_assert(pa_cvolume_equal(&i->soft_volume, &i->volume_factor));
+         }
+@@ -2329,3 +2329,27 @@ void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume)
+     pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+     pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED], i);
+ }
++
++/* Called from the main thread. */
++void pa_sink_input_set_reference_ratio(pa_sink_input *i, const pa_cvolume *ratio) {
++    pa_cvolume old_ratio;
++    char old_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++    char new_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++
++    pa_assert(i);
++    pa_assert(ratio);
++
++    old_ratio = i->reference_ratio;
++
++    if (pa_cvolume_equal(ratio, &old_ratio))
++        return;
++
++    i->reference_ratio = *ratio;
++
++    if (!PA_SINK_INPUT_IS_LINKED(i->state))
++        return;
++
++    pa_log_debug("Sink input %u reference ratio changed from %s to %s.", i->index,
++                 pa_cvolume_snprint_verbose(old_ratio_str, sizeof(old_ratio_str), &old_ratio, &i->channel_map, true),
++                 pa_cvolume_snprint_verbose(new_ratio_str, sizeof(new_ratio_str), ratio, &i->channel_map, true));
++}
+diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
+index 1bd3eee..e5b0ae8 100644
+--- a/src/pulsecore/sink-input.h
++++ b/src/pulsecore/sink-input.h
+@@ -437,6 +437,12 @@ pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret);
+  * and fires change notifications. */
+ void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume);
++/* Called from the main thread, from sink.c only. This shouldn't be a public
++ * function, but the flat volume logic in sink.c currently needs a way to
++ * directly set the sink input reference ratio. This function simply sets
++ * i->reference_ratio and logs a message if the value changes. */
++void pa_sink_input_set_reference_ratio(pa_sink_input *i, const pa_cvolume *ratio);
++
+ #define pa_sink_input_assert_io_context(s) \
+     pa_assert(pa_thread_mq_get() || !PA_SINK_INPUT_IS_LINKED((s)->state))
+diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
+index 41cffcc..400d06d 100644
+--- a/src/pulsecore/sink.c
++++ b/src/pulsecore/sink.c
+@@ -1670,6 +1670,7 @@ void pa_sink_leave_passthrough(pa_sink *s) {
+ static void compute_reference_ratio(pa_sink_input *i) {
+     unsigned c = 0;
+     pa_cvolume remapped;
++    pa_cvolume ratio;
+     pa_assert(i);
+     pa_assert(pa_sink_flat_volume_enabled(i->sink));
+@@ -1684,7 +1685,7 @@ static void compute_reference_ratio(pa_sink_input *i) {
+     remapped = i->sink->reference_volume;
+     pa_cvolume_remap(&remapped, &i->sink->channel_map, &i->channel_map);
+-    i->reference_ratio.channels = i->sample_spec.channels;
++    ratio = i->reference_ratio;
+     for (c = 0; c < i->sample_spec.channels; c++) {
+@@ -1694,14 +1695,16 @@ static void compute_reference_ratio(pa_sink_input *i) {
+         /* Don't update the reference ratio unless necessary */
+         if (pa_sw_volume_multiply(
+-                    i->reference_ratio.values[c],
++                    ratio.values[c],
+                     remapped.values[c]) == i->volume.values[c])
+             continue;
+-        i->reference_ratio.values[c] = pa_sw_volume_divide(
++        ratio.values[c] = pa_sw_volume_divide(
+                 i->volume.values[c],
+                 remapped.values[c]);
+     }
++
++    pa_sink_input_set_reference_ratio(i, &ratio);
+ }
+ /* Called from main context. Only called for the root sink in volume sharing
+@@ -2195,7 +2198,7 @@ static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume)
+             /* 2. Since the sink's reference and real volumes are equal
+              * now our ratios should be too. */
+-            i->reference_ratio = i->real_ratio;
++            pa_sink_input_set_reference_ratio(i, &i->real_ratio);
+             /* 3. Recalculate the new stream reference volume based on the
+              * reference ratio and the sink's reference volume.
+diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
+index d3d15f1..0012be3 100644
+--- a/src/pulsecore/source-output.c
++++ b/src/pulsecore/source-output.c
+@@ -1313,7 +1313,7 @@ static void update_volume_due_to_moving(pa_source_output *o, pa_source *dest) {
+             pa_cvolume_reset(&new_volume, o->volume.channels);
+             pa_source_output_set_volume_direct(o, &new_volume);
+-            pa_cvolume_reset(&o->reference_ratio, o->reference_ratio.channels);
++            pa_source_output_set_reference_ratio(o, &new_volume);
+             pa_assert(pa_cvolume_is_norm(&o->real_ratio));
+             pa_assert(pa_cvolume_equal(&o->soft_volume, &o->volume_factor));
+         }
+@@ -1695,3 +1695,27 @@ void pa_source_output_set_volume_direct(pa_source_output *o, const pa_cvolume *v
+     pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+     pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED], o);
+ }
++
++/* Called from the main thread. */
++void pa_source_output_set_reference_ratio(pa_source_output *o, const pa_cvolume *ratio) {
++    pa_cvolume old_ratio;
++    char old_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++    char new_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
++
++    pa_assert(o);
++    pa_assert(ratio);
++
++    old_ratio = o->reference_ratio;
++
++    if (pa_cvolume_equal(ratio, &old_ratio))
++        return;
++
++    o->reference_ratio = *ratio;
++
++    if (!PA_SOURCE_OUTPUT_IS_LINKED(o->state))
++        return;
++
++    pa_log_debug("Source output %u reference ratio changed from %s to %s.", o->index,
++                 pa_cvolume_snprint_verbose(old_ratio_str, sizeof(old_ratio_str), &old_ratio, &o->channel_map, true),
++                 pa_cvolume_snprint_verbose(new_ratio_str, sizeof(new_ratio_str), ratio, &o->channel_map, true));
++}
+diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
+index 73170d3..3ed950b 100644
+--- a/src/pulsecore/source-output.h
++++ b/src/pulsecore/source-output.h
+@@ -359,6 +359,12 @@ pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output
+  * o->volume and fires change notifications. */
+ void pa_source_output_set_volume_direct(pa_source_output *o, const pa_cvolume *volume);
++/* Called from the main thread, from source.c only. This shouldn't be a public
++ * function, but the flat volume logic in source.c currently needs a way to
++ * directly set the source output reference ratio. This function simply sets
++ * o->reference_ratio and logs a message if the value changes. */
++void pa_source_output_set_reference_ratio(pa_source_output *o, const pa_cvolume *ratio);
++
+ #define pa_source_output_assert_io_context(s) \
+     pa_assert(pa_thread_mq_get() || !PA_SOURCE_OUTPUT_IS_LINKED((s)->state))
+diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
+index db7f667..293ab28 100644
+--- a/src/pulsecore/source.c
++++ b/src/pulsecore/source.c
+@@ -1214,6 +1214,7 @@ void pa_source_leave_passthrough(pa_source *s) {
+ static void compute_reference_ratio(pa_source_output *o) {
+     unsigned c = 0;
+     pa_cvolume remapped;
++    pa_cvolume ratio;
+     pa_assert(o);
+     pa_assert(pa_source_flat_volume_enabled(o->source));
+@@ -1228,7 +1229,7 @@ static void compute_reference_ratio(pa_source_output *o) {
+     remapped = o->source->reference_volume;
+     pa_cvolume_remap(&remapped, &o->source->channel_map, &o->channel_map);
+-    o->reference_ratio.channels = o->sample_spec.channels;
++    ratio = o->reference_ratio;
+     for (c = 0; c < o->sample_spec.channels; c++) {
+@@ -1238,14 +1239,16 @@ static void compute_reference_ratio(pa_source_output *o) {
+         /* Don't update the reference ratio unless necessary */
+         if (pa_sw_volume_multiply(
+-                    o->reference_ratio.values[c],
++                    ratio.values[c],
+                     remapped.values[c]) == o->volume.values[c])
+             continue;
+-        o->reference_ratio.values[c] = pa_sw_volume_divide(
++        ratio.values[c] = pa_sw_volume_divide(
+                 o->volume.values[c],
+                 remapped.values[c]);
+     }
++
++    pa_source_output_set_reference_ratio(o, &ratio);
+ }
+ /* Called from main context. Only called for the root source in volume sharing
+@@ -1712,7 +1715,7 @@ static void propagate_real_volume(pa_source *s, const pa_cvolume *old_real_volum
+             /* 2. Since the source's reference and real volumes are equal
+              * now our ratios should be too. */
+-            o->reference_ratio = o->real_ratio;
++            pa_source_output_set_reference_ratio(o, &o->real_ratio);
+             /* 3. Recalculate the new stream reference volume based on the
+              * reference ratio and the sink's reference volume.
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0100-sink-input-source-output-Add-hooks-for-reference-rat.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0100-sink-input-source-output-Add-hooks-for-reference-rat.patch
new file mode 100644 (file)
index 0000000..cfbfdde
--- /dev/null
@@ -0,0 +1,87 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 4 Aug 2014 21:26:17 +0300
+Subject: sink-input, source-output: Add hooks for reference ratio changes
+
+Needed for implementing relative volume controls for streams in
+module-volume-api. The plan is to create those controls in the core,
+though, and these hooks won't be needed at that point any more.
+
+Change-Id: Id30f38f4adfa9ede7bd0b12b484fe329ca1a3991
+---
+ src/pulsecore/core.h          | 2 ++
+ src/pulsecore/sink-input.c    | 2 ++
+ src/pulsecore/sink-input.h    | 3 ++-
+ src/pulsecore/source-output.c | 2 ++
+ src/pulsecore/source-output.h | 3 ++-
+ 5 files changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
+index 1f042b9..0e8f709 100644
+--- a/src/pulsecore/core.h
++++ b/src/pulsecore/core.h
+@@ -100,6 +100,7 @@ typedef enum pa_core_hook {
+     PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED,
+     PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED,
+     PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED,
++    PA_CORE_HOOK_SINK_INPUT_REFERENCE_RATIO_CHANGED,
+     PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED,
+     PA_CORE_HOOK_SINK_INPUT_SEND_EVENT,
+     PA_CORE_HOOK_SOURCE_OUTPUT_NEW,
+@@ -113,6 +114,7 @@ typedef enum pa_core_hook {
+     PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED,
+     PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED,
+     PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED,
++    PA_CORE_HOOK_SOURCE_OUTPUT_REFERENCE_RATIO_CHANGED,
+     PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED,
+     PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT,
+     PA_CORE_HOOK_CLIENT_NEW,
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index 57c906d..d62be6f 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -2352,4 +2352,6 @@ void pa_sink_input_set_reference_ratio(pa_sink_input *i, const pa_cvolume *ratio
+     pa_log_debug("Sink input %u reference ratio changed from %s to %s.", i->index,
+                  pa_cvolume_snprint_verbose(old_ratio_str, sizeof(old_ratio_str), &old_ratio, &i->channel_map, true),
+                  pa_cvolume_snprint_verbose(new_ratio_str, sizeof(new_ratio_str), ratio, &i->channel_map, true));
++
++    pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_REFERENCE_RATIO_CHANGED], i);
+ }
+diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
+index e5b0ae8..c99ce1f 100644
+--- a/src/pulsecore/sink-input.h
++++ b/src/pulsecore/sink-input.h
+@@ -440,7 +440,8 @@ void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume)
+ /* Called from the main thread, from sink.c only. This shouldn't be a public
+  * function, but the flat volume logic in sink.c currently needs a way to
+  * directly set the sink input reference ratio. This function simply sets
+- * i->reference_ratio and logs a message if the value changes. */
++ * i->reference_ratio and logs a message and fires a hook if the value
++ * changes. */
+ void pa_sink_input_set_reference_ratio(pa_sink_input *i, const pa_cvolume *ratio);
+ #define pa_sink_input_assert_io_context(s) \
+diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
+index 0012be3..ae5a92c 100644
+--- a/src/pulsecore/source-output.c
++++ b/src/pulsecore/source-output.c
+@@ -1718,4 +1718,6 @@ void pa_source_output_set_reference_ratio(pa_source_output *o, const pa_cvolume
+     pa_log_debug("Source output %u reference ratio changed from %s to %s.", o->index,
+                  pa_cvolume_snprint_verbose(old_ratio_str, sizeof(old_ratio_str), &old_ratio, &o->channel_map, true),
+                  pa_cvolume_snprint_verbose(new_ratio_str, sizeof(new_ratio_str), ratio, &o->channel_map, true));
++
++    pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_REFERENCE_RATIO_CHANGED], o);
+ }
+diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
+index 3ed950b..60bbda8 100644
+--- a/src/pulsecore/source-output.h
++++ b/src/pulsecore/source-output.h
+@@ -362,7 +362,8 @@ void pa_source_output_set_volume_direct(pa_source_output *o, const pa_cvolume *v
+ /* Called from the main thread, from source.c only. This shouldn't be a public
+  * function, but the flat volume logic in source.c currently needs a way to
+  * directly set the source output reference ratio. This function simply sets
+- * o->reference_ratio and logs a message if the value changes. */
++ * o->reference_ratio and logs a message and fires a hook if the value
++ * changes. */
+ void pa_source_output_set_reference_ratio(pa_source_output *o, const pa_cvolume *ratio);
+ #define pa_source_output_assert_io_context(s) \
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0101-sink-input-source-output-Use-new_data.volume-only-fo.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0101-sink-input-source-output-Use-new_data.volume-only-fo.patch
new file mode 100644 (file)
index 0000000..13d5221
--- /dev/null
@@ -0,0 +1,411 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Mon, 4 Aug 2014 21:42:45 +0300
+Subject: sink-input,
+ source-output: Use new_data.volume only for absolute volume
+
+This simplifies life for modules that care about the initial volume of
+streams. new_data.volume will always be the absolute volume (assuming
+that flat volume is in effect) and new_data.reference_ratio will
+always be the relative volume.
+
+This will be especially useful when creating volume controls (absolute
+and relative) for new streams in module-volume-api.
+
+Change-Id: Ibca033c8441dde35a0b43d9276c41e383c675306
+---
+ src/modules/module-match.c          |  2 +-
+ src/modules/module-stream-restore.c |  8 ++---
+ src/pulsecore/play-memblockq.c      |  2 +-
+ src/pulsecore/protocol-native.c     |  6 ++--
+ src/pulsecore/sink-input.c          | 71 +++++++++++++++++++++++++------------
+ src/pulsecore/sink-input.h          |  5 +--
+ src/pulsecore/sound-file-stream.c   |  2 +-
+ src/pulsecore/source-output.c       | 71 +++++++++++++++++++++++++------------
+ src/pulsecore/source-output.h       |  5 +--
+ 9 files changed, 111 insertions(+), 61 deletions(-)
+
+diff --git a/src/modules/module-match.c b/src/modules/module-match.c
+index 8ce3f00..7d73086 100644
+--- a/src/modules/module-match.c
++++ b/src/modules/module-match.c
+@@ -234,7 +234,7 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu
+                 pa_cvolume cv;
+                 pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume);
+                 pa_cvolume_set(&cv, si->sample_spec.channels, r->volume);
+-                pa_sink_input_new_data_set_volume(si, &cv);
++                pa_sink_input_new_data_set_volume(si, &cv, true);
+             } else
+                 pa_log_debug("the volume of sink input '%s' is not writable, can't change it", n);
+         }
+diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c
+index 4fc5645..1aa8a07 100644
+--- a/src/modules/module-stream-restore.c
++++ b/src/modules/module-stream-restore.c
+@@ -1482,9 +1482,7 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu
+                 v = e->volume;
+                 pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map);
+-                pa_sink_input_new_data_set_volume(new_data, &v);
+-
+-                new_data->volume_is_absolute = false;
++                pa_sink_input_new_data_set_volume(new_data, &v, true);
+                 new_data->save_volume = true;
+             }
+         }
+@@ -1579,9 +1577,7 @@ static pa_hook_result_t source_output_fixate_hook_callback(pa_core *c, pa_source
+                 v = e->volume;
+                 pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map);
+-                pa_source_output_new_data_set_volume(new_data, &v);
+-
+-                new_data->volume_is_absolute = false;
++                pa_source_output_new_data_set_volume(new_data, &v, true);
+                 new_data->save_volume = true;
+             }
+         }
+diff --git a/src/pulsecore/play-memblockq.c b/src/pulsecore/play-memblockq.c
+index ff0c52b..055fd2a 100644
+--- a/src/pulsecore/play-memblockq.c
++++ b/src/pulsecore/play-memblockq.c
+@@ -203,7 +203,7 @@ pa_sink_input* pa_memblockq_sink_input_new(
+     data.driver = __FILE__;
+     pa_sink_input_new_data_set_sample_spec(&data, ss);
+     pa_sink_input_new_data_set_channel_map(&data, map);
+-    pa_sink_input_new_data_set_volume(&data, volume);
++    pa_sink_input_new_data_set_volume(&data, volume, false);
+     pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p);
+     data.flags |= flags;
+diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
+index 21e02fe..b209737 100644
+--- a/src/pulsecore/protocol-native.c
++++ b/src/pulsecore/protocol-native.c
+@@ -679,8 +679,7 @@ static record_stream* record_stream_new(
+         pa_source_output_new_data_set_formats(&data, formats);
+     data.direct_on_input = direct_on_input;
+     if (volume) {
+-        pa_source_output_new_data_set_volume(&data, volume);
+-        data.volume_is_absolute = !relative_volume;
++        pa_source_output_new_data_set_volume(&data, volume, relative_volume);
+         data.save_volume = false;
+     }
+     if (muted_set) {
+@@ -1149,8 +1148,7 @@ static playback_stream* playback_stream_new(
+         formats = NULL;
+     }
+     if (volume) {
+-        pa_sink_input_new_data_set_volume(&data, volume);
+-        data.volume_is_absolute = !relative_volume;
++        pa_sink_input_new_data_set_volume(&data, volume, relative_volume);
+         data.save_volume = false;
+     }
+     if (muted_set) {
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index d62be6f..796a567 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -145,12 +145,44 @@ bool pa_sink_input_new_data_is_passthrough(pa_sink_input_new_data *data) {
+     return false;
+ }
+-void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume) {
++void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume, bool relative) {
++    pa_cvolume remapped_sink_volume;
++
+     pa_assert(data);
+     pa_assert(data->volume_writable);
+     if ((data->volume_is_set = !!volume))
+         data->volume = *volume;
++    else
++        return;
++
++    data->volume_is_relative = relative;
++
++    if (data->sink) {
++        remapped_sink_volume = data->sink->reference_volume;
++        pa_cvolume_remap(&remapped_sink_volume, &data->sink->channel_map, &data->channel_map);
++    }
++
++    if (relative) {
++        data->reference_ratio = data->volume;
++
++        if (data->sink && pa_sink_flat_volume_enabled(data->sink)) {
++            /* Let's keep data->volume as absolute, so that modules won't ever
++             * have to specially handle the relative case. Modules inspecting
++             * the volume should do so in the FIXATE hook, and at that point
++             * data->sink is always set. data->volume is relative only during
++             * the time before routing, and only if the sink input owner
++             * requested relative volume. */
++            pa_sw_cvolume_multiply(&data->volume, &data->volume, &remapped_sink_volume);
++            data->volume_is_relative = false;
++        }
++    } else {
++        if (data->sink)
++            pa_sw_cvolume_divide(&data->reference_ratio, &data->volume, &remapped_sink_volume);
++
++        /* If data->sink is not set, we can't compute the reference ratio.
++         * We'll compute it after routing. */
++    }
+ }
+ void pa_sink_input_new_data_add_volume_factor(pa_sink_input_new_data *data, const char *key, const pa_cvolume *volume_factor) {
+@@ -292,6 +324,7 @@ int pa_sink_input_new(
+     int r;
+     char *pt;
+     char *memblockq_name;
++    pa_cvolume v;
+     pa_assert(_i);
+     pa_assert(core);
+@@ -380,18 +413,17 @@ int pa_sink_input_new(
+     if (r != PA_OK)
+         return r;
++    /* Now that the routing is done, we can finalize the volume if it has been
++     * set. If the set volume is relative, we convert it to absolute, and if
++     * it's absolute, we compute the reference ratio. */
++    if (data->volume_is_set)
++        pa_sink_input_new_data_set_volume(data, &data->volume, data->volume_is_relative);
++
+     /* Don't restore (or save) stream volume for passthrough streams and
+      * prevent attenuation/gain */
+     if (pa_sink_input_new_data_is_passthrough(data)) {
+-        data->volume_is_set = true;
+-        pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+-        data->volume_is_absolute = true;
+-        data->save_volume = false;
+-    }
+-
+-    if (!data->volume_is_set) {
+-        pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+-        data->volume_is_absolute = false;
++        pa_cvolume_reset(&v, data->sample_spec.channels);
++        pa_sink_input_new_data_set_volume(data, &v, false);
+         data->save_volume = false;
+     }
+@@ -432,6 +464,12 @@ int pa_sink_input_new(
+     if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data)) < 0)
+         return r;
++    if (!data->volume_is_set) {
++        pa_cvolume_reset(&v, data->sample_spec.channels);
++        pa_sink_input_new_data_set_volume(data, &v, true);
++        data->save_volume = false;
++    }
++
+     if ((data->flags & PA_SINK_INPUT_NO_CREATE_ON_SUSPEND) &&
+         pa_sink_get_state(data->sink) == PA_SINK_SUSPENDED) {
+         pa_log_warn("Failed to create sink input: sink is suspended.");
+@@ -482,18 +520,7 @@ int pa_sink_input_new(
+     i->sample_spec = data->sample_spec;
+     i->channel_map = data->channel_map;
+     i->format = pa_format_info_copy(data->format);
+-
+-    if (!data->volume_is_absolute && pa_sink_flat_volume_enabled(i->sink)) {
+-        pa_cvolume remapped;
+-
+-        /* When the 'absolute' bool is not set then we'll treat the volume
+-         * as relative to the sink volume even in flat volume mode */
+-        remapped = data->sink->reference_volume;
+-        pa_cvolume_remap(&remapped, &data->sink->channel_map, &data->channel_map);
+-        pa_sw_cvolume_multiply(&i->volume, &data->volume, &remapped);
+-    } else
+-        i->volume = data->volume;
+-
++    i->volume = data->volume;
+     i->volume_factor_items = data->volume_factor_items;
+     data->volume_factor_items = NULL;
+     volume_factor_from_hashmap(&i->volume_factor, i->volume_factor_items, i->sample_spec.channels);
+diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
+index c99ce1f..b2d4967 100644
+--- a/src/pulsecore/sink-input.h
++++ b/src/pulsecore/sink-input.h
+@@ -311,6 +311,7 @@ typedef struct pa_sink_input_new_data {
+     pa_idxset *nego_formats;
+     pa_cvolume volume;
++    pa_cvolume reference_ratio;
+     bool muted:1;
+     pa_hashmap *volume_factor_items, *volume_factor_sink_items;
+@@ -320,7 +321,7 @@ typedef struct pa_sink_input_new_data {
+     bool volume_is_set:1;
+     bool muted_is_set:1;
+-    bool volume_is_absolute:1;
++    bool volume_is_relative:1;
+     bool volume_writable:1;
+@@ -331,7 +332,7 @@ pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data
+ void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec);
+ void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map);
+ bool pa_sink_input_new_data_is_passthrough(pa_sink_input_new_data *data);
+-void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume);
++void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume, bool relative);
+ void pa_sink_input_new_data_add_volume_factor(pa_sink_input_new_data *data, const char *key, const pa_cvolume *volume_factor);
+ void pa_sink_input_new_data_add_volume_factor_sink(pa_sink_input_new_data *data, const char *key, const pa_cvolume *volume_factor);
+ void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, bool mute);
+diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c
+index 33f7337..6886025 100644
+--- a/src/pulsecore/sound-file-stream.c
++++ b/src/pulsecore/sound-file-stream.c
+@@ -302,7 +302,7 @@ int pa_play_file(
+     data.driver = __FILE__;
+     pa_sink_input_new_data_set_sample_spec(&data, &ss);
+     pa_sink_input_new_data_set_channel_map(&data, &cm);
+-    pa_sink_input_new_data_set_volume(&data, volume);
++    pa_sink_input_new_data_set_volume(&data, volume, false);
+     pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, pa_path_get_filename(fname));
+     pa_proplist_sets(data.proplist, PA_PROP_MEDIA_FILENAME, fname);
+     pa_sndfile_init_proplist(u->sndfile, data.proplist);
+diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
+index ae5a92c..c929999 100644
+--- a/src/pulsecore/source-output.c
++++ b/src/pulsecore/source-output.c
+@@ -86,12 +86,44 @@ bool pa_source_output_new_data_is_passthrough(pa_source_output_new_data *data) {
+     return false;
+ }
+-void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume) {
++void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume, bool relative) {
++    pa_cvolume remapped_source_volume;
++
+     pa_assert(data);
+     pa_assert(data->volume_writable);
+     if ((data->volume_is_set = !!volume))
+         data->volume = *volume;
++    else
++        return;
++
++    data->volume_is_relative = relative;
++
++    if (data->source) {
++        remapped_source_volume = data->source->reference_volume;
++        pa_cvolume_remap(&remapped_source_volume, &data->source->channel_map, &data->channel_map);
++    }
++
++    if (relative) {
++        data->reference_ratio = data->volume;
++
++        if (data->source && pa_source_flat_volume_enabled(data->source)) {
++            /* Let's keep data->volume as absolute, so that modules won't ever
++             * have to specially handle the relative case. Modules inspecting
++             * the volume should do so in the FIXATE hook, and at that point
++             * data->source is always set. data->volume is relative only during
++             * the time before routing, and only if the source output owner
++             * requested relative volume. */
++            pa_sw_cvolume_multiply(&data->volume, &data->volume, &remapped_source_volume);
++            data->volume_is_relative = false;
++        }
++    } else {
++        if (data->source)
++            pa_sw_cvolume_divide(&data->reference_ratio, &data->volume, &remapped_source_volume);
++
++        /* If data->source is not set, we can't compute the reference ratio.
++         * We'll compute it after routing. */
++    }
+ }
+ void pa_source_output_new_data_apply_volume_factor(pa_source_output_new_data *data, const pa_cvolume *volume_factor) {
+@@ -226,6 +258,7 @@ int pa_source_output_new(
+     pa_channel_map volume_map;
+     int r;
+     char *pt;
++    pa_cvolume v;
+     pa_assert(_o);
+     pa_assert(core);
+@@ -316,18 +349,17 @@ int pa_source_output_new(
+     if (r < 0)
+         return r;
++    /* Now that the routing is done, we can finalize the volume if it has been
++     * set. If the set volume is relative, we convert it to absolute, and if
++     * it's absolute, we compute the reference ratio. */
++    if (data->volume_is_set)
++        pa_source_output_new_data_set_volume(data, &data->volume, data->volume_is_relative);
++
+     /* Don't restore (or save) stream volume for passthrough streams and
+      * prevent attenuation/gain */
+     if (pa_source_output_new_data_is_passthrough(data)) {
+-        data->volume_is_set = true;
+-        pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+-        data->volume_is_absolute = true;
+-        data->save_volume = false;
+-    }
+-
+-    if (!data->volume_is_set) {
+-        pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+-        data->volume_is_absolute = false;
++        pa_cvolume_reset(&v, data->sample_spec.channels);
++        pa_source_output_new_data_set_volume(data, &v, false);
+         data->save_volume = false;
+     }
+@@ -378,6 +410,12 @@ int pa_source_output_new(
+     if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data)) < 0)
+         return r;
++    if (!data->volume_is_set) {
++        pa_cvolume_reset(&v, data->sample_spec.channels);
++        pa_source_output_new_data_set_volume(data, &v, true);
++        data->save_volume = false;
++    }
++
+     if ((data->flags & PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND) &&
+         pa_source_get_state(data->source) == PA_SOURCE_SUSPENDED) {
+         pa_log("Failed to create source output: source is suspended.");
+@@ -427,18 +465,7 @@ int pa_source_output_new(
+     o->sample_spec = data->sample_spec;
+     o->channel_map = data->channel_map;
+     o->format = pa_format_info_copy(data->format);
+-
+-    if (!data->volume_is_absolute && pa_source_flat_volume_enabled(o->source)) {
+-        pa_cvolume remapped;
+-
+-        /* When the 'absolute' bool is not set then we'll treat the volume
+-         * as relative to the source volume even in flat volume mode */
+-        remapped = data->source->reference_volume;
+-        pa_cvolume_remap(&remapped, &data->source->channel_map, &data->channel_map);
+-        pa_sw_cvolume_multiply(&o->volume, &data->volume, &remapped);
+-    } else
+-        o->volume = data->volume;
+-
++    o->volume = data->volume;
+     o->volume_factor = data->volume_factor;
+     o->volume_factor_source = data->volume_factor_source;
+     o->real_ratio = o->reference_ratio = data->volume;
+diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
+index 60bbda8..dc82af9 100644
+--- a/src/pulsecore/source-output.h
++++ b/src/pulsecore/source-output.h
+@@ -257,6 +257,7 @@ typedef struct pa_source_output_new_data {
+     pa_idxset *nego_formats;
+     pa_cvolume volume, volume_factor, volume_factor_source;
++    pa_cvolume reference_ratio;
+     bool muted:1;
+     bool sample_spec_is_set:1;
+@@ -265,7 +266,7 @@ typedef struct pa_source_output_new_data {
+     bool volume_is_set:1, volume_factor_is_set:1, volume_factor_source_is_set:1;
+     bool muted_is_set:1;
+-    bool volume_is_absolute:1;
++    bool volume_is_relative:1;
+     bool volume_writable:1;
+@@ -276,7 +277,7 @@ pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_d
+ void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec);
+ void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map);
+ bool pa_source_output_new_data_is_passthrough(pa_source_output_new_data *data);
+-void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume);
++void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume, bool relative);
+ void pa_source_output_new_data_apply_volume_factor(pa_source_output_new_data *data, const pa_cvolume *volume_factor);
+ void pa_source_output_new_data_apply_volume_factor_source(pa_source_output_new_data *data, const pa_cvolume *volume_factor);
+ void pa_source_output_new_data_set_muted(pa_source_output_new_data *data, bool mute);
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0102-sink-input-source-output-Add-the-real-object-pointer.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0102-sink-input-source-output-Add-the-real-object-pointer.patch
new file mode 100644 (file)
index 0000000..35b67f5
--- /dev/null
@@ -0,0 +1,543 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Tue, 5 Aug 2014 12:01:54 +0300
+Subject: sink-input, source-output: Add the real object pointer to new_data
+
+module-volume-api needs the pointer already in the FIXATE hook, where
+it creates the volume control objects. The pointer is needed, because
+otherwise there's no way to correlate the created controls with the
+sink inputs and source outputs.
+
+Since the object is created early, pa_sink_input_new() and
+pa_source_output_new() need to free it in case of failure, so a bunch
+of direct returns were replaced with "goto fails".
+
+Since the object may now be unlinked in a completely uninitialized
+state, I reviewed the unlinking code, and made sure that unlinking is
+performed only once (the "unlinked" flag was needed for this).
+
+Change-Id: I89bee3fb51c54d270ccf856750c5b577babc7905
+---
+ src/pulsecore/sink-input.c    |  96 ++++++++++++++++++++++++++------------
+ src/pulsecore/sink-input.h    |   6 +++
+ src/pulsecore/source-output.c | 105 ++++++++++++++++++++++++++++++------------
+ src/pulsecore/source-output.h |   6 +++
+ 4 files changed, 155 insertions(+), 58 deletions(-)
+
+diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
+index 796a567..d419884 100644
+--- a/src/pulsecore/sink-input.c
++++ b/src/pulsecore/sink-input.c
+@@ -331,6 +331,9 @@ int pa_sink_input_new(
+     pa_assert(data);
+     pa_assert_ctl_context();
++    i = data->sink_input = pa_msgobject_new(pa_sink_input);
++    i->state = PA_SINK_INPUT_INIT;
++
+     if (data->client)
+         pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist);
+@@ -348,22 +351,31 @@ int pa_sink_input_new(
+                                              !(data->flags & PA_SINK_INPUT_FIX_FORMAT),
+                                              !(data->flags & PA_SINK_INPUT_FIX_RATE),
+                                              !(data->flags & PA_SINK_INPUT_FIX_CHANNELS));
+-        if (!f)
+-            return -PA_ERR_INVALID;
++        if (!f) {
++            r = -PA_ERR_INVALID;
++            goto fail;
++        }
+         formats = pa_idxset_new(NULL, NULL);
+         pa_idxset_put(formats, f, NULL);
+         pa_sink_input_new_data_set_formats(data, formats);
+     }
+-    if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], data)) < 0)
+-        return r;
++    pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], data);
+-    pa_return_val_if_fail(!data->driver || pa_utf8_valid(data->driver), -PA_ERR_INVALID);
++    if (data->driver && !pa_utf8_valid(data->driver)) {
++        r = -PA_ERR_INVALID;
++        goto fail;
++    }
+     if (!data->sink) {
+         pa_sink *sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK);
+-        pa_return_val_if_fail(sink, -PA_ERR_NOENTITY);
++
++        if (!sink) {
++            r = -PA_ERR_NOENTITY;
++            goto fail;
++        }
++
+         pa_sink_input_new_data_set_sink(data, sink, false);
+     }
+@@ -382,13 +394,20 @@ int pa_sink_input_new(
+         PA_IDXSET_FOREACH(format, data->req_formats, idx)
+             pa_log_info(" -- %s", pa_format_info_snprint(fmt, sizeof(fmt), format));
+-        return -PA_ERR_NOTSUPPORTED;
++        r = -PA_ERR_NOTSUPPORTED;
++        goto fail;
++    }
++
++    if (!PA_SINK_IS_LINKED(pa_sink_get_state(data->sink))) {
++        r = -PA_ERR_BADSTATE;
++        goto fail;
+     }
+-    pa_return_val_if_fail(PA_SINK_IS_LINKED(pa_sink_get_state(data->sink)), -PA_ERR_BADSTATE);
+-    pa_return_val_if_fail(!data->sync_base || (data->sync_base->sink == data->sink
+-                                               && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED),
+-                          -PA_ERR_INVALID);
++    if (data->sync_base
++            && !(data->sync_base->sink == data->sink && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED)) {
++        r = -PA_ERR_INVALID;
++        goto fail;
++    }
+     /* Routing is done. We have a sink and a format. */
+@@ -399,7 +418,7 @@ int pa_sink_input_new(
+          * modified in pa_format_info_to_sample_spec2(). */
+         r = pa_stream_get_volume_channel_map(&data->volume, data->channel_map_is_set ? &data->channel_map : NULL, data->format, &volume_map);
+         if (r < 0)
+-            return r;
++            goto fail;
+     }
+     /* Now populate the sample spec and channel map according to the final
+@@ -407,11 +426,11 @@ int pa_sink_input_new(
+     r = pa_format_info_to_sample_spec2(data->format, &data->sample_spec, &data->channel_map, &data->sink->sample_spec,
+                                        &data->sink->channel_map);
+     if (r < 0)
+-        return r;
++        goto fail;
+     r = check_passthrough_connection(pa_sink_input_new_data_is_passthrough(data), data->sink);
+     if (r != PA_OK)
+-        return r;
++        goto fail;
+     /* Now that the routing is done, we can finalize the volume if it has been
+      * set. If the set volume is relative, we convert it to absolute, and if
+@@ -453,16 +472,19 @@ int pa_sink_input_new(
+         /* rate update failed, or other parts of sample spec didn't match */
+         pa_log_debug("Could not update sink sample spec to match passthrough stream");
+-        return -PA_ERR_NOTSUPPORTED;
++        r = -PA_ERR_NOTSUPPORTED;
++        goto fail;
+     }
+     if (data->resample_method == PA_RESAMPLER_INVALID)
+         data->resample_method = core->resample_method;
+-    pa_return_val_if_fail(data->resample_method < PA_RESAMPLER_MAX, -PA_ERR_INVALID);
++    if (data->resample_method >= PA_RESAMPLER_MAX) {
++        r = -PA_ERR_INVALID;
++        goto fail;
++    }
+-    if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data)) < 0)
+-        return r;
++    pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data);
+     if (!data->volume_is_set) {
+         pa_cvolume_reset(&v, data->sample_spec.channels);
+@@ -473,12 +495,14 @@ int pa_sink_input_new(
+     if ((data->flags & PA_SINK_INPUT_NO_CREATE_ON_SUSPEND) &&
+         pa_sink_get_state(data->sink) == PA_SINK_SUSPENDED) {
+         pa_log_warn("Failed to create sink input: sink is suspended.");
+-        return -PA_ERR_BADSTATE;
++        r = -PA_ERR_BADSTATE;
++        goto fail;
+     }
+     if (pa_idxset_size(data->sink->inputs) >= PA_MAX_INPUTS_PER_SINK) {
+         pa_log_warn("Failed to create sink input: too many inputs per sink.");
+-        return -PA_ERR_TOOLARGE;
++        r = -PA_ERR_TOOLARGE;
++        goto fail;
+     }
+     if ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ||
+@@ -497,16 +521,15 @@ int pa_sink_input_new(
+                           (core->disable_remixing || (data->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
+                           (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) {
+                 pa_log_warn("Unsupported resampling operation.");
+-                return -PA_ERR_NOTSUPPORTED;
++                r = -PA_ERR_NOTSUPPORTED;
++                goto fail;
+             }
+     }
+-    i = pa_msgobject_new(pa_sink_input);
+     i->parent.parent.free = sink_input_free;
+     i->parent.process_msg = pa_sink_input_process_msg;
+     i->core = core;
+-    i->state = PA_SINK_INPUT_INIT;
+     i->flags = data->flags;
+     i->proplist = pa_proplist_copy(data->proplist);
+     i->driver = pa_xstrdup(pa_path_get_filename(data->driver));
+@@ -610,6 +633,16 @@ int pa_sink_input_new(
+     *_i = i;
+     return 0;
++
++fail:
++    if (i) {
++        pa_sink_input_unlink(i);
++        pa_sink_input_unref(i);
++    }
++
++    data->sink_input = NULL;
++
++    return r;
+ }
+ /* Called from main context */
+@@ -683,6 +716,11 @@ void pa_sink_input_unlink(pa_sink_input *i) {
+     pa_assert(i);
+     pa_assert_ctl_context();
++    if (i->unlinked)
++        return;
++
++    i->unlinked = true;
++
+     /* See pa_sink_unlink() for a couple of comments how this function
+      * works */
+@@ -691,7 +729,9 @@ void pa_sink_input_unlink(pa_sink_input *i) {
+     linked = PA_SINK_INPUT_IS_LINKED(i->state);
+     if (linked)
+-        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], i);
++        pa_idxset_remove_by_data(i->core->sink_inputs, i, NULL);
++
++    pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], i);
+     if (i->sync_prev)
+         i->sync_prev->sync_next = i->sync_next;
+@@ -700,8 +740,6 @@ void pa_sink_input_unlink(pa_sink_input *i) {
+     i->sync_prev = i->sync_next = NULL;
+-    pa_idxset_remove_by_data(i->core->sink_inputs, i, NULL);
+-
+     if (i->sink)
+         if (pa_idxset_remove_by_data(i->sink->inputs, i, NULL))
+             pa_sink_input_unref(i);
+@@ -732,10 +770,10 @@ void pa_sink_input_unlink(pa_sink_input *i) {
+     reset_callbacks(i);
+-    if (linked) {
++    if (linked)
+         pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE, i->index);
+-        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], i);
+-    }
++
++    pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], i);
+     if (i->sink) {
+         if (PA_SINK_IS_LINKED(pa_sink_get_state(i->sink)))
+diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
+index b2d4967..a4c6519 100644
+--- a/src/pulsecore/sink-input.h
++++ b/src/pulsecore/sink-input.h
+@@ -76,6 +76,7 @@ struct pa_sink_input {
+      * pa_sink_input_get_state(). That function will transparently
+      * merge the thread_info.drained value in. */
+     pa_sink_input_state_t state;
++    bool unlinked;
+     pa_sink_input_flags_t flags;
+     char *driver;                       /* may be NULL */
+@@ -289,6 +290,11 @@ typedef struct pa_sink_input_send_event_hook_data {
+ } pa_sink_input_send_event_hook_data;
+ typedef struct pa_sink_input_new_data {
++    /* The sink input object is not properly initialized, so don't access the
++     * member variables! You can only rely on the state variable being
++     * initialized to PA_SINK_INPUT_INIT. */
++    pa_sink_input *sink_input;
++
+     pa_sink_input_flags_t flags;
+     pa_proplist *proplist;
+diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
+index c929999..60f3391 100644
+--- a/src/pulsecore/source-output.c
++++ b/src/pulsecore/source-output.c
+@@ -265,6 +265,9 @@ int pa_source_output_new(
+     pa_assert(data);
+     pa_assert_ctl_context();
++    o = data->source_output = pa_msgobject_new(pa_source_output);
++    o->state = PA_SOURCE_OUTPUT_INIT;
++
+     if (data->client)
+         pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist);
+@@ -282,28 +285,38 @@ int pa_source_output_new(
+                                              !(data->flags & PA_SOURCE_OUTPUT_FIX_FORMAT),
+                                              !(data->flags & PA_SOURCE_OUTPUT_FIX_RATE),
+                                              !(data->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS));
+-        if (!f)
+-            return -PA_ERR_INVALID;
++        if (!f) {
++            r = -PA_ERR_INVALID;
++            goto fail;
++        }
+         formats = pa_idxset_new(NULL, NULL);
+         pa_idxset_put(formats, f, NULL);
+         pa_source_output_new_data_set_formats(data, formats);
+     }
+-    if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data)) < 0)
+-        return r;
++    pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data);
+-    pa_return_val_if_fail(!data->driver || pa_utf8_valid(data->driver), -PA_ERR_INVALID);
++    if (data->driver && !pa_utf8_valid(data->driver)) {
++        r = -PA_ERR_INVALID;
++        goto fail;
++    }
+     if (!data->source) {
+         pa_source *source;
+         if (data->direct_on_input) {
+             source = data->direct_on_input->sink->monitor_source;
+-            pa_return_val_if_fail(source, -PA_ERR_INVALID);
++            if (!source) {
++                r = -PA_ERR_INVALID;
++                goto fail;
++            }
+         } else {
+             source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE);
+-            pa_return_val_if_fail(source, -PA_ERR_NOENTITY);
++            if (!source) {
++                r = -PA_ERR_NOENTITY;
++                goto fail;
++            }
+         }
+         pa_source_output_new_data_set_source(data, source, false);
+@@ -324,11 +337,19 @@ int pa_source_output_new(
+         PA_IDXSET_FOREACH(format, data->req_formats, idx)
+             pa_log_info(" -- %s", pa_format_info_snprint(fmt, sizeof(fmt), format));
+-        return -PA_ERR_NOTSUPPORTED;
++        r = -PA_ERR_NOTSUPPORTED;
++        goto fail;
+     }
+-    pa_return_val_if_fail(PA_SOURCE_IS_LINKED(pa_source_get_state(data->source)), -PA_ERR_BADSTATE);
+-    pa_return_val_if_fail(!data->direct_on_input || data->direct_on_input->sink == data->source->monitor_of, -PA_ERR_INVALID);
++    if (!PA_SOURCE_IS_LINKED(pa_source_get_state(data->source))) {
++        r = -PA_ERR_BADSTATE;
++        goto fail;
++    }
++
++    if (data->direct_on_input && data->direct_on_input->sink != data->source->monitor_of) {
++        r = -PA_ERR_INVALID;
++        goto fail;
++    }
+     /* Routing is done. We have a source and a format. */
+@@ -339,7 +360,7 @@ int pa_source_output_new(
+          * modified in pa_format_info_to_sample_spec2(). */
+         r = pa_stream_get_volume_channel_map(&data->volume, data->channel_map_is_set ? &data->channel_map : NULL, data->format, &volume_map);
+         if (r < 0)
+-            return r;
++            goto fail;
+     }
+     /* Now populate the sample spec and channel map according to the final
+@@ -347,7 +368,7 @@ int pa_source_output_new(
+     r = pa_format_info_to_sample_spec2(data->format, &data->sample_spec, &data->channel_map, &data->source->sample_spec,
+                                        &data->source->channel_map);
+     if (r < 0)
+-        return r;
++        goto fail;
+     /* Now that the routing is done, we can finalize the volume if it has been
+      * set. If the set volume is relative, we convert it to absolute, and if
+@@ -374,12 +395,18 @@ int pa_source_output_new(
+     if (!data->volume_factor_is_set)
+         pa_cvolume_reset(&data->volume_factor, data->sample_spec.channels);
+-    pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor, &data->sample_spec), -PA_ERR_INVALID);
++    if (!pa_cvolume_compatible(&data->volume_factor, &data->sample_spec)) {
++        r = -PA_ERR_INVALID;
++        goto fail;
++    }
+     if (!data->volume_factor_source_is_set)
+         pa_cvolume_reset(&data->volume_factor_source, data->source->sample_spec.channels);
+-    pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor_source, &data->source->sample_spec), -PA_ERR_INVALID);
++    if (!pa_cvolume_compatible(&data->volume_factor_source, &data->source->sample_spec)) {
++        r = -PA_ERR_INVALID;
++        goto fail;
++    }
+     if (!data->muted_is_set)
+         data->muted = false;
+@@ -399,16 +426,19 @@ int pa_source_output_new(
+         /* rate update failed, or other parts of sample spec didn't match */
+         pa_log_debug("Could not update source sample spec to match passthrough stream");
+-        return -PA_ERR_NOTSUPPORTED;
++        r = -PA_ERR_NOTSUPPORTED;
++        goto fail;
+     }
+     if (data->resample_method == PA_RESAMPLER_INVALID)
+         data->resample_method = core->resample_method;
+-    pa_return_val_if_fail(data->resample_method < PA_RESAMPLER_MAX, -PA_ERR_INVALID);
++    if (data->resample_method >= PA_RESAMPLER_MAX) {
++        r = -PA_ERR_INVALID;
++        goto fail;
++    }
+-    if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data)) < 0)
+-        return r;
++    pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data);
+     if (!data->volume_is_set) {
+         pa_cvolume_reset(&v, data->sample_spec.channels);
+@@ -419,12 +449,14 @@ int pa_source_output_new(
+     if ((data->flags & PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND) &&
+         pa_source_get_state(data->source) == PA_SOURCE_SUSPENDED) {
+         pa_log("Failed to create source output: source is suspended.");
+-        return -PA_ERR_BADSTATE;
++        r = -PA_ERR_BADSTATE;
++        goto fail;
+     }
+     if (pa_idxset_size(data->source->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) {
+         pa_log("Failed to create source output: too many outputs per source.");
+-        return -PA_ERR_TOOLARGE;
++        r = -PA_ERR_TOOLARGE;
++        goto fail;
+     }
+     if ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ||
+@@ -442,16 +474,15 @@ int pa_source_output_new(
+                         (core->disable_remixing || (data->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
+                         (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) {
+                 pa_log_warn("Unsupported resampling operation.");
+-                return -PA_ERR_NOTSUPPORTED;
++                r = -PA_ERR_NOTSUPPORTED;
++                goto fail;
+             }
+     }
+-    o = pa_msgobject_new(pa_source_output);
+     o->parent.parent.free = source_output_free;
+     o->parent.process_msg = pa_source_output_process_msg;
+     o->core = core;
+-    o->state = PA_SOURCE_OUTPUT_INIT;
+     o->flags = data->flags;
+     o->proplist = pa_proplist_copy(data->proplist);
+     o->driver = pa_xstrdup(pa_path_get_filename(data->driver));
+@@ -526,6 +557,16 @@ int pa_source_output_new(
+     *_o = o;
+     return 0;
++
++fail:
++    if (o) {
++        pa_source_output_unlink(o);
++        pa_source_output_unref(o);
++    }
++
++    data->source_output = NULL;
++
++    return r;
+ }
+ /* Called from main context */
+@@ -576,9 +617,15 @@ static void source_output_set_state(pa_source_output *o, pa_source_output_state_
+ /* Called from main context */
+ void pa_source_output_unlink(pa_source_output*o) {
+     bool linked;
++
+     pa_assert(o);
+     pa_assert_ctl_context();
++    if (o->unlinked)
++        return;
++
++    o->unlinked = true;
++
+     /* See pa_sink_unlink() for a couple of comments how this function
+      * works */
+@@ -587,13 +634,13 @@ void pa_source_output_unlink(pa_source_output*o) {
+     linked = PA_SOURCE_OUTPUT_IS_LINKED(o->state);
+     if (linked)
+-        pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], o);
++        pa_idxset_remove_by_data(o->core->source_outputs, o, NULL);
++
++    pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], o);
+     if (o->direct_on_input)
+         pa_idxset_remove_by_data(o->direct_on_input->direct_outputs, o, NULL);
+-    pa_idxset_remove_by_data(o->core->source_outputs, o, NULL);
+-
+     if (o->source)
+         if (pa_idxset_remove_by_data(o->source->outputs, o, NULL))
+             pa_source_output_unref(o);
+@@ -618,10 +665,10 @@ void pa_source_output_unlink(pa_source_output*o) {
+     reset_callbacks(o);
+-    if (linked) {
++    if (linked)
+         pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_REMOVE, o->index);
+-        pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], o);
+-    }
++
++    pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], o);
+     if (o->source) {
+         if (PA_SOURCE_IS_LINKED(pa_source_get_state(o->source)))
+diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
+index dc82af9..af6d347 100644
+--- a/src/pulsecore/source-output.h
++++ b/src/pulsecore/source-output.h
+@@ -69,6 +69,7 @@ struct pa_source_output {
+     pa_core *core;
+     pa_source_output_state_t state;
++    bool unlinked;
+     pa_source_output_flags_t flags;
+     char *driver;                         /* may be NULL */
+@@ -236,6 +237,11 @@ typedef struct pa_source_output_send_event_hook_data {
+ } pa_source_output_send_event_hook_data;
+ typedef struct pa_source_output_new_data {
++    /* The source output object is not properly initialized, so don't access
++     * the member variables! You can only rely on the state variable being
++     * initialized to PA_SOURCE_OUTPUT_INIT. */
++    pa_source_output *source_output;
++
+     pa_source_output_flags_t flags;
+     pa_proplist *proplist;
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0/0103-audio-groups-main-volume-policy-volume-api-Various-f.patch b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0103-audio-groups-main-volume-policy-volume-api-Various-f.patch
new file mode 100644 (file)
index 0000000..626d911
--- /dev/null
@@ -0,0 +1,9676 @@
+From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
+Date: Tue, 17 Jun 2014 19:45:45 +0300
+Subject: audio-groups, main-volume-policy, volume-api: Various fixes
+
+Sorry, this is a huge unreviewable commit. Contained improvements
+include at least:
+
+ * Flat volumes are now handled properly. Previously, audio groups
+   controlled the absolute volume of streams if flat volume was in
+   effect, which made no sense.
+ * Audio group volumes are now persistent.
+ * Audio group volumes are applied to new streams before the streams
+   start to play, instead of after, which could cause audible
+   glitches.
+ * When a stream volume is changed by the user, the volume is
+   propagated to the stream's audio group.
+ * Fixed the handling of the "NEG" keyword in the match syntax in
+   module-audio-groups. Previously the "NEG" keyword was parsed, but
+   it had no effect.
+
+Change-Id: I02bad3d23b3e562c71dbc6af6f3e308089893751
+---
+ src/Makefile.am                                    |    2 +-
+ src/map-file                                       |    3 +
+ src/modules/audio-groups/audio-groups.conf.example |   19 +-
+ src/modules/audio-groups/module-audio-groups.c     | 1710 ++++++++++++++------
+ .../main-volume-policy/main-volume-context.c       |  184 +--
+ .../main-volume-policy/main-volume-context.h       |   35 +-
+ .../main-volume-policy/main-volume-policy.c        |   65 +-
+ .../main-volume-policy.conf.example                |    1 -
+ .../main-volume-policy/main-volume-policy.h        |    6 +-
+ .../main-volume-policy/module-main-volume-policy.c |  651 +++++---
+ src/modules/volume-api/audio-group.c               |  288 +---
+ src/modules/volume-api/audio-group.h               |   29 +-
+ src/modules/volume-api/binding.c                   |  386 -----
+ src/modules/volume-api/binding.h                   |  128 --
+ src/modules/volume-api/bvolume.h                   |    3 +
+ src/modules/volume-api/device-creator.c            |  431 +++--
+ src/modules/volume-api/device.c                    |   54 +-
+ src/modules/volume-api/device.h                    |    4 +-
+ src/modules/volume-api/inidb.c                     |  553 +++++++
+ src/modules/volume-api/inidb.h                     |   54 +
+ src/modules/volume-api/module-volume-api.c         |   27 +
+ src/modules/volume-api/mute-control.c              |  241 +--
+ src/modules/volume-api/mute-control.h              |   75 +-
+ src/modules/volume-api/sstream.c                   |  313 ++--
+ src/modules/volume-api/sstream.h                   |   56 +-
+ src/modules/volume-api/stream-creator.c            |  691 ++++----
+ src/modules/volume-api/volume-api.c                |  430 +++--
+ src/modules/volume-api/volume-api.h                |   79 +-
+ src/modules/volume-api/volume-control.c            |  308 ++--
+ src/modules/volume-api/volume-control.h            |   93 +-
+ src/pulse/ext-volume-api.c                         |  100 ++
+ src/pulse/ext-volume-api.h                         |    3 +
+ src/tizen-ivi/audio-groups.conf                    |   23 +-
+ src/tizen-ivi/main-volume-policy.conf              |    1 -
+ 34 files changed, 4012 insertions(+), 3034 deletions(-)
+ delete mode 100644 src/modules/volume-api/binding.c
+ delete mode 100644 src/modules/volume-api/binding.h
+ create mode 100644 src/modules/volume-api/inidb.c
+ create mode 100644 src/modules/volume-api/inidb.h
+
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 9d17336..b5cf2a8 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -1098,10 +1098,10 @@ 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/inidb.c modules/volume-api/inidb.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 \
+diff --git a/src/map-file b/src/map-file
+index 28ea54e..cb31833 100644
+--- a/src/map-file
++++ b/src/map-file
+@@ -190,13 +190,16 @@ 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_balance_to_string;
+ 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;
+ pa_ext_volume_api_bvolume_init_invalid;
+ pa_ext_volume_api_bvolume_init_mono;
++pa_ext_volume_api_bvolume_parse_balance;
+ pa_ext_volume_api_bvolume_remap;
+ pa_ext_volume_api_bvolume_reset_balance;
+ pa_ext_volume_api_bvolume_set_left_right_balance;
+diff --git a/src/modules/audio-groups/audio-groups.conf.example b/src/modules/audio-groups/audio-groups.conf.example
+index 8acdb76..6aa6989 100644
+--- a/src/modules/audio-groups/audio-groups.conf.example
++++ b/src/modules/audio-groups/audio-groups.conf.example
+@@ -1,28 +1,29 @@
+ [General]
+-audio-groups = x-example-call-downlink-audio-group x-example-default-output-audio-group x-example-music-output-audio-group
+-streams = phone-output music-output default-output
++stream-rules = phone-output music-output default-output
+ [AudioGroup x-example-call-downlink-audio-group]
+-volume-control = create
+-mute-control = none
++volume-control = create:call-downlink-volume-control
++mute-control = create:call-downlink-mute-control
+ [AudioGroup x-example-default-output-audio-group]
+-volume-control = create
+-mute-control = none
++volume-control = create:default-output-volume-control
++mute-control = create:call-downlink-mute-control
+ [AudioGroup x-example-music-output-audio-group]
+ volume-control = bind:AudioGroup:x-example-default-output-audio-group
++mute-control = bind:AudioGroup:x-example-default-output-audio-group
+-[Stream phone-output]
++[StreamRule phone-output]
+ match = (direction output AND property media.role=phone)
+ audio-group-for-volume = x-example-call-downlink-audio-group
+ audio-group-for-mute = x-example-call-downlink-audio-group
+-[Stream music-output]
++[StreamRule music-output]
+ match = (direction output AND property media.role=music)
+ audio-group-for-volume = x-example-music-output-audio-group
+ audio-group-for-mute = x-example-music-output-audio-group
+-[Stream default-output]
++[StreamRule default-output]
++match = (direction output)
+ audio-group-for-volume = x-example-default-output-audio-group
+ audio-group-for-mute = x-example-default-output-audio-group
+diff --git a/src/modules/audio-groups/module-audio-groups.c b/src/modules/audio-groups/module-audio-groups.c
+index 2b3a570..e37a24e 100644
+--- a/src/modules/audio-groups/module-audio-groups.c
++++ b/src/modules/audio-groups/module-audio-groups.c
+@@ -40,15 +40,18 @@
+ #include "module-audio-groups-symdef.h"
++#define AUDIOGROUP_START "AudioGroup "
++#define STREAM_RULE_START "StreamRule "
++#define NONE_KEYWORD "none"
++#define CREATE_PREFIX "create:"
++#define BIND_PREFIX "bind:"
++#define BIND_AUDIO_GROUP_PREFIX BIND_PREFIX "AudioGroup:"
++
+ PA_MODULE_AUTHOR("Ismo Puustinen");
+ PA_MODULE_DESCRIPTION("Create audio groups and classify streams to them");
+ PA_MODULE_VERSION(PACKAGE_VERSION);
+ PA_MODULE_LOAD_ONCE(true);
+-#ifndef AUDIO_GROUP_CONFIG
+-#define AUDIO_GROUP_CONFIG "audio-groups.conf"
+-#endif
+-
+ enum match_direction {
+     match_direction_unknown = 0,
+     match_direction_input,
+@@ -80,60 +83,103 @@ struct expression {
+     PA_LLIST_HEAD(struct conjunction, conjunctions);
+ };
+-/* data gathered from settings */
++struct group {
++    struct userdata *userdata;
++    pa_audio_group *audio_group;
++    struct control *volume_control;
++    struct control *mute_control;
++    char *own_volume_control_name;
++    char *own_mute_control_name;
++    struct group *volume_master;
++    struct group *mute_master;
++    char *volume_master_name;
++    char *mute_master_name;
++
++    pa_hashmap *volume_slaves; /* struct group -> struct group (hashmap-as-a-set) */
++    pa_hashmap *mute_slaves; /* struct group -> struct group (hashmap-as-a-set) */
++    pa_hashmap *volume_stream_rules; /* struct stream_rule -> struct stream_rule (hashmap-as-a-set) */
++    pa_hashmap *mute_stream_rules; /* struct stream_rule -> struct stream_rule (hashmap-as-a-set) */
++
++    bool unlinked;
++};
+-enum control_action {
+-    CONTROL_ACTION_NONE,
+-    CONTROL_ACTION_CREATE,
+-    CONTROL_ACTION_BIND,
++enum control_type {
++    CONTROL_TYPE_VOLUME,
++    CONTROL_TYPE_MUTE,
+ };
+-struct audio_group {
++struct control {
+     struct userdata *userdata;
+-    char *id;
+-    char *description;
+-    enum control_action volume_control_action;
+-    enum control_action mute_control_action;
+-    pa_binding_target_info *volume_control_target_info;
+-    pa_binding_target_info *mute_control_target_info;
++    enum control_type type;
++
++    union {
++        pa_volume_control *volume_control;
++        pa_mute_control *mute_control;
++    };
++
++    /* Controls that are created for streams don't own their pa_volume_control
++     * and pa_mute_control objects, because they're owned by the streams. */
++    bool own_control;
+-    /* official audio group */
+-    pa_audio_group *group;
++    /* If non-NULL, then this control mirrors the state of the master
++     * control. If someone changes the master state, the state of this control
++     * is also updated, and also if someone changes this control's state, the
++     * change is applied also to the master. */
++    struct control *master;
+-    struct audio_group_control *volume_control;
+-    struct audio_group_control *mute_control;
++    /* struct control -> struct control (hashmap-as-a-set)
++     * Contains the controls that have this control as their master. */
++    pa_hashmap *slaves;
++    /* Set to true when the master control's state has been copied to this
++     * control. */
++    bool synced_with_master;
++
++    bool acquired;
+     bool unlinked;
+ };
+-struct stream {
++struct stream_rule {
+     struct userdata *userdata;
+-    char *id;
++    char *name;
+     enum match_direction direction;
+     char *audio_group_name_for_volume;
+     char *audio_group_name_for_mute;
+-    pa_audio_group *audio_group_for_volume;
+-    pa_audio_group *audio_group_for_mute;
+-    pa_binding_target_info *volume_control_target_info;
+-    pa_binding_target_info *mute_control_target_info;
+-    struct expression *rule;
+-
+-    bool unlinked;
++    struct group *group_for_volume;
++    struct group *group_for_mute;
++    struct expression *match_expression;
+ };
+ struct userdata {
+-    pa_hashmap *audio_groups; /* name -> struct audio_group */
+-    pa_dynarray *streams; /* struct stream */
+-    pa_hook_slot *new_stream_volume;
+-    pa_hook_slot *new_stream_mute;
+-
+-    pa_volume_api *api;
+-
+-    /* The following fields are only used during initialization. */
+-    pa_hashmap *audio_group_names; /* name -> name (hashmap-as-a-set) */
+-    pa_hashmap *unused_audio_groups; /* name -> struct audio_group */
+-    pa_dynarray *stream_names;
+-    pa_hashmap *unused_streams; /* name -> struct stream */
++    pa_volume_api *volume_api;
++    pa_hashmap *groups; /* name -> struct group */
++    pa_hashmap *stream_rules; /* name -> struct stream_rule */
++    pa_dynarray *stream_rules_list; /* struct stream_rule */
++
++    /* pas_stream -> struct stream_rule
++     * When a stream matches with a rule, it's added here. */
++    pa_hashmap *rules_by_stream;
++
++    /* pas_stream -> struct control
++     * Contains proxy controls for all relative volume controls of streams. */
++    pa_hashmap *stream_volume_controls;
++
++    /* pas_stream -> struct control
++     * Contains proxy controls for all mute controls of streams. */
++    pa_hashmap *stream_mute_controls;
++
++    pa_hook_slot *stream_put_slot;
++    pa_hook_slot *stream_unlink_slot;
++    pa_hook_slot *volume_control_implementation_initialized_slot;
++    pa_hook_slot *mute_control_implementation_initialized_slot;
++    pa_hook_slot *volume_control_set_initial_volume_slot;
++    pa_hook_slot *mute_control_set_initial_mute_slot;
++    pa_hook_slot *volume_control_volume_changed_slot;
++    pa_hook_slot *mute_control_mute_changed_slot;
++    pa_hook_slot *volume_control_unlink_slot;
++    pa_hook_slot *mute_control_unlink_slot;
++
++    pa_dynarray *stream_rule_names; /* Only used during initialization. */
+ };
+ static const char* const valid_modargs[] = {
+@@ -141,77 +187,408 @@ static const char* const valid_modargs[] = {
+     NULL
+ };
+-static void audio_group_unlink(struct audio_group *group);
++static void control_free(struct control *control);
++static void control_set_master(struct control *control, struct control *master);
++static void control_add_slave(struct control *control, struct control *slave);
++static void control_remove_slave(struct control *control, struct control *slave);
+-static void print_literal(struct literal *l);
+-static void print_conjunction(struct conjunction *c);
+-static void print_expression(struct expression *e);
+-static void delete_expression(struct expression *e);
++static void group_free(struct group *group);
++static void group_set_master(struct group *group, enum control_type type, struct group *master);
++static int group_set_master_name(struct group *group, enum control_type type, const char *name);
++static void group_disable_control(struct group *group, enum control_type type);
++static void group_add_slave(struct group *group, enum control_type type, struct group *slave);
++static void group_remove_slave(struct group *group, enum control_type type, struct group *slave);
+-static struct audio_group *audio_group_new(struct userdata *u, const char *name) {
+-    struct audio_group *group;
++static void stream_rule_set_group(struct stream_rule *rule, enum control_type type, struct group *group);
++static void stream_rule_set_group_name(struct stream_rule *rule, enum control_type type, const char *name);
+-    pa_assert(u);
+-    pa_assert(name);
++static bool literal_match(struct literal *literal, pas_stream *stream);
+-    group = pa_xnew0(struct audio_group, 1);
+-    group->userdata = u;
+-    group->id = pa_xstrdup(name);
+-    group->description = pa_xstrdup(name);
+-    group->volume_control_action = CONTROL_ACTION_NONE;
+-    group->mute_control_action = CONTROL_ACTION_NONE;
++static struct expression *expression_new(void);
++static void expression_free(struct expression *expression);
++
++static int volume_control_set_volume_cb(pa_volume_control *volume_control, const pa_bvolume *original_volume,
++                                        const pa_bvolume *remapped_volume, bool set_volume, bool set_balance) {
++    struct control *control;
++    struct control *slave;
++    void *state;
++
++    pa_assert(volume_control);
++    pa_assert(original_volume);
++    pa_assert(remapped_volume);
++
++    control = volume_control->userdata;
++
++    /* There are four cases that need to be considered:
++     *
++     * 1) The master control is propagating the volume to this control. We need
++     * to propagate the volume downstream.
++     *
++     * 2) This control was just assigned a master control and the volume hasn't
++     * yet been synchronized. In this case the volume that is now being set for
++     * this control is the master control's volume. We need to propagate the
++     * volume downstream.
++     *
++     * 3) Someone set the volume directly for this control, and this control
++     * has a master control. We need to propagate the volume upstream, and wait
++     * for another call that will fall under the case 1.
++     *
++     * 4) Someone set the volume directly for this control, and this control
++     * doesn't have a master control. We need to propagate the volume
++     * downstream.
++     *
++     * As we can see, the action is the same in cases 1, 2 and 4. */
++
++    /* Case 3. */
++    if (control->synced_with_master && !control->master->volume_control->set_volume_in_progress) {
++        pa_volume_control_set_volume(control->master->volume_control, original_volume, set_volume, set_balance);
++        return 0;
++    }
++
++    /* Cases 1, 2 and 4. */
++    PA_HASHMAP_FOREACH(slave, control->slaves, state)
++        pa_volume_control_set_volume(slave->volume_control, original_volume, set_volume, set_balance);
+-    return group;
++    return 0;
+ }
+-static int audio_group_put(struct audio_group *group) {
+-    int r;
++static int mute_control_set_mute_cb(pa_mute_control *mute_control, bool mute) {
++    struct control *control;
++    struct control *slave;
++    void *state;
++
++    pa_assert(mute_control);
++
++    control = mute_control->userdata;
++
++    /* There are four cases that need to be considered:
++     *
++     * 1) The master control is propagating the mute to this control. We need
++     * to propagate the mute downstream.
++     *
++     * 2) This control was just assigned a master control and the mute hasn't
++     * yet been synchronized. In this case the mute that is now being set for
++     * this control is the master control's mute. We need to propagate the mute
++     * downstream.
++     *
++     * 3) Someone set the mute directly for this control, and this control has
++     * a master control. We need to propagate the mute upstream, and wait for
++     * another call that will fall under the case 1.
++     *
++     * 4) Someone set the mute directly for this control, and this control
++     * doesn't have a master control. We need to propagate the mute downstream.
++     *
++     * As we can see, the action is the same in cases 1, 2 and 4. */
++
++    /* Case 3. */
++    if (control->synced_with_master && !control->master->mute_control->set_mute_in_progress) {
++        pa_mute_control_set_mute(control->master->mute_control, mute);
++        return 0;
++    }
++
++    /* Cases 1, 2 and 4. */
++    PA_HASHMAP_FOREACH(slave, control->slaves, state)
++        pa_mute_control_set_mute(slave->mute_control, mute);
++
++    return 0;
++}
++
++static int control_new_for_group(struct group *group, enum control_type type, const char *name, bool persistent, struct control **_r) {
++    struct control *control = NULL;
++    int r = 0;
+     pa_assert(group);
++    pa_assert(name);
++    pa_assert(_r);
++
++    control = pa_xnew0(struct control, 1);
++    control->userdata = group->userdata;
++    control->type = type;
++    control->slaves = pa_hashmap_new(NULL, NULL);
++
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            if (persistent)
++                control->volume_control = pa_hashmap_get(control->userdata->volume_api->volume_controls, name);
++
++            if (!control->volume_control) {
++                r = pa_volume_control_new(control->userdata->volume_api, name, persistent, &control->volume_control);
++                if (r < 0)
++                    goto fail;
++            }
+-    r = pa_audio_group_new(group->userdata->api, group->id, group->description, &group->group);
+-    if (r < 0)
+-        goto fail;
++            pa_volume_control_set_convertible_to_dB(control->volume_control, true);
++
++            if (persistent) {
++                r = pa_volume_control_acquire_for_audio_group(control->volume_control, group->audio_group,
++                                                              volume_control_set_volume_cb, control);
++                if (r < 0)
++                    goto fail;
++
++                control->acquired = true;
++            } else {
++                control->volume_control->set_volume = volume_control_set_volume_cb;
++                control->volume_control->userdata = control;
++            }
++            break;
++
++        case CONTROL_TYPE_MUTE:
++            if (persistent)
++                control->mute_control = pa_hashmap_get(control->userdata->volume_api->mute_controls, name);
++
++            if (!control->mute_control) {
++                r = pa_mute_control_new(control->userdata->volume_api, name, persistent, &control->mute_control);
++                if (r < 0)
++                    goto fail;
++            }
++
++            if (persistent) {
++                r = pa_mute_control_acquire_for_audio_group(control->mute_control, group->audio_group,
++                                                            mute_control_set_mute_cb, control);
++                if (r < 0)
++                    goto fail;
++
++                control->acquired = true;
++            } else {
++                control->mute_control->set_mute = mute_control_set_mute_cb;
++                control->mute_control->userdata = control;
++            }
++            break;
++    }
++
++    control->own_control = true;
++
++    *_r = control;
++    return 0;
++
++fail:
++    if (control)
++        control_free(control);
++
++    return r;
++}
++
++static struct control *control_new_for_stream(struct userdata *u, enum control_type type, pas_stream *stream) {
++    struct control *control;
++
++    pa_assert(u);
++    pa_assert(stream);
+-    switch (group->volume_control_action) {
+-        case CONTROL_ACTION_NONE:
++    control = pa_xnew0(struct control, 1);
++    control->userdata = u;
++    control->type = type;
++
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            control->volume_control = stream->relative_volume_control;
++            pa_assert(control->volume_control);
++            break;
++
++        case CONTROL_TYPE_MUTE:
++            control->mute_control = stream->mute_control;
++            pa_assert(control->mute_control);
+             break;
++    }
+-        case CONTROL_ACTION_CREATE:
+-            pa_audio_group_set_have_own_volume_control(group->group, true);
+-            pa_audio_group_set_volume_control(group->group, group->group->own_volume_control);
++    return control;
++}
++
++static void control_put(struct control *control) {
++    pa_assert(control);
++
++    switch (control->type) {
++        case CONTROL_TYPE_VOLUME:
++            if (control->own_control && !control->volume_control->linked)
++                pa_volume_control_put(control->volume_control);
+             break;
+-        case CONTROL_ACTION_BIND:
+-            pa_audio_group_bind_volume_control(group->group, group->volume_control_target_info);
++        case CONTROL_TYPE_MUTE:
++            if (control->own_control && !control->mute_control->linked)
++                pa_mute_control_put(control->mute_control);
+             break;
+     }
++}
++
++static void control_unlink(struct control *control) {
++    pa_assert(control);
++
++    if (control->unlinked)
++        return;
++
++    control->unlinked = true;
++
++    if (control->slaves) {
++        struct control *slave;
++
++        while ((slave = pa_hashmap_first(control->slaves)))
++            control_set_master(slave, NULL);
++    }
++
++    control_set_master(control, NULL);
+-    switch (group->mute_control_action) {
+-        case CONTROL_ACTION_NONE:
++    switch (control->type) {
++        case CONTROL_TYPE_VOLUME:
++            if (control->own_control && control->volume_control && !control->volume_control->persistent)
++                pa_volume_control_unlink(control->volume_control);
+             break;
+-        case CONTROL_ACTION_CREATE:
+-            pa_audio_group_set_have_own_mute_control(group->group, true);
+-            pa_audio_group_set_mute_control(group->group, group->group->own_mute_control);
++        case CONTROL_TYPE_MUTE:
++            if (control->own_control && control->mute_control && !control->mute_control->persistent)
++                pa_mute_control_unlink(control->mute_control);
+             break;
++    }
++}
++
++static void control_free(struct control *control) {
++    pa_assert(control);
++
++    if (!control->unlinked)
++        control_unlink(control);
++
++    if (control->slaves) {
++        pa_assert(pa_hashmap_isempty(control->slaves));
++        pa_hashmap_free(control->slaves);
++    }
++
++    switch (control->type) {
++        case CONTROL_TYPE_VOLUME:
++            if (control->acquired)
++                pa_volume_control_release(control->volume_control);
++
++            if (control->own_control && control->volume_control && !control->volume_control->persistent)
++                pa_volume_control_free(control->volume_control);
++            break;
++
++        case CONTROL_TYPE_MUTE:
++            if (control->acquired)
++                pa_mute_control_release(control->mute_control);
+-        case CONTROL_ACTION_BIND:
+-            pa_audio_group_bind_mute_control(group->group, group->mute_control_target_info);
++            if (control->own_control && control->mute_control && !control->mute_control->persistent)
++                pa_mute_control_free(control->mute_control);
+             break;
+     }
+-    pa_audio_group_put(group->group);
++    pa_xfree(control);
++}
++
++static void control_set_master(struct control *control, struct control *master) {
++    struct control *old_master;
++
++    pa_assert(control);
++    pa_assert(!master || master->type == control->type);
++
++    old_master = control->master;
++
++    if (master == old_master)
++        return;
++
++    if (old_master) {
++        control_remove_slave(old_master, control);
++        control->synced_with_master = false;
++    }
++
++    control->master = master;
++
++    if (master) {
++        control_add_slave(master, control);
++
++        switch (control->type) {
++            case CONTROL_TYPE_VOLUME:
++                pa_volume_control_set_volume(control->volume_control, &master->volume_control->volume, true, true);
++                break;
++
++            case CONTROL_TYPE_MUTE:
++                pa_mute_control_set_mute(control->mute_control, master->mute_control->mute);
++                break;
++        }
++
++        control->synced_with_master = true;
++    }
++}
++
++static void control_add_slave(struct control *control, struct control *slave) {
++    pa_assert(control);
++    pa_assert(slave);
++
++    pa_assert_se(pa_hashmap_put(control->slaves, slave, slave) >= 0);
++}
++
++static void control_remove_slave(struct control *control, struct control *slave) {
++    pa_assert(control);
++    pa_assert(slave);
++
++    pa_assert_se(pa_hashmap_remove(control->slaves, slave));
++}
++
++static int group_new(struct userdata *u, const char *name, struct group **_r) {
++    struct group *group = NULL;
++    int r;
++    struct group *slave;
++    struct stream_rule *rule;
++    void *state;
++
++    pa_assert(u);
++    pa_assert(name);
++    pa_assert(_r);
++
++    group = pa_xnew0(struct group, 1);
++    group->userdata = u;
++
++    r = pa_audio_group_new(u->volume_api, name, &group->audio_group);
++    if (r < 0)
++        goto fail;
++
++    group->volume_slaves = pa_hashmap_new(NULL, NULL);
++    group->mute_slaves = pa_hashmap_new(NULL, NULL);
++    group->volume_stream_rules = pa_hashmap_new(NULL, NULL);
++    group->mute_stream_rules = pa_hashmap_new(NULL, NULL);
++
++    PA_HASHMAP_FOREACH(slave, u->groups, state) {
++        if (slave == group)
++            continue;
++
++        if (pa_safe_streq(slave->volume_master_name, group->audio_group->name))
++            group_set_master(slave, CONTROL_TYPE_VOLUME, group);
++
++        if (pa_safe_streq(slave->mute_master_name, group->audio_group->name))
++            group_set_master(slave, CONTROL_TYPE_MUTE, group);
++    }
++
++    PA_HASHMAP_FOREACH(rule, u->stream_rules, state) {
++        if (pa_safe_streq(rule->audio_group_name_for_volume, group->audio_group->name))
++            stream_rule_set_group(rule, CONTROL_TYPE_VOLUME, group);
++        if (pa_safe_streq(rule->audio_group_name_for_mute, group->audio_group->name))
++            stream_rule_set_group(rule, CONTROL_TYPE_MUTE, group);
++    }
++
++    *_r = group;
+     return 0;
+ fail:
+-    audio_group_unlink(group);
++    if (group)
++        group_free(group);
+     return r;
+ }
+-static void audio_group_unlink(struct audio_group *group) {
++static void group_put(struct group *group) {
++    pa_assert(group);
++
++    pa_audio_group_put(group->audio_group);
++
++    if (group->volume_control)
++        control_put(group->volume_control);
++
++    if (group->mute_control)
++        control_put(group->mute_control);
++}
++
++static void group_unlink(struct group *group) {
++    struct stream_rule *rule;
++    struct group *slave;
++    void *state;
++
+     pa_assert(group);
+     if (group->unlinked)
+@@ -219,192 +596,406 @@ static void audio_group_unlink(struct audio_group *group) {
+     group->unlinked = true;
+-    if (group->group) {
+-        pa_audio_group_free(group->group);
+-        group->group = NULL;
+-    }
++    PA_HASHMAP_FOREACH(rule, group->volume_stream_rules, state)
++        stream_rule_set_group(rule, CONTROL_TYPE_VOLUME, NULL);
++
++    PA_HASHMAP_FOREACH(rule, group->mute_stream_rules, state)
++        stream_rule_set_group(rule, CONTROL_TYPE_MUTE, NULL);
++
++    PA_HASHMAP_FOREACH(slave, group->volume_slaves, state)
++        group_set_master(slave, CONTROL_TYPE_VOLUME, NULL);
++
++    PA_HASHMAP_FOREACH(slave, group->mute_slaves, state)
++        group_set_master(slave, CONTROL_TYPE_MUTE, NULL);
++
++    group_disable_control(group, CONTROL_TYPE_MUTE);
++    group_disable_control(group, CONTROL_TYPE_VOLUME);
++
++    if (group->audio_group)
++        pa_audio_group_unlink(group->audio_group);
+ }
+-static void audio_group_free(struct audio_group *group) {
++static void group_free(struct group *group) {
+     pa_assert(group);
+-    if (!group->unlinked)
+-        audio_group_unlink(group);
++    group_unlink(group);
++
++    if (group->mute_stream_rules) {
++        pa_assert(pa_hashmap_isempty(group->mute_stream_rules));
++        pa_hashmap_free(group->mute_stream_rules);
++    }
++
++    if (group->volume_stream_rules) {
++        pa_assert(pa_hashmap_isempty(group->volume_stream_rules));
++        pa_hashmap_free(group->volume_stream_rules);
++    }
++
++    if (group->mute_slaves) {
++        pa_assert(pa_hashmap_isempty(group->mute_slaves));
++        pa_hashmap_free(group->mute_slaves);
++    }
++
++    if (group->volume_slaves) {
++        pa_assert(pa_hashmap_isempty(group->volume_slaves));
++        pa_hashmap_free(group->volume_slaves);
++    }
+-    if (group->mute_control_target_info)
+-        pa_binding_target_info_free(group->mute_control_target_info);
++    pa_assert(!group->mute_master_name);
++    pa_assert(!group->volume_master_name);
++    pa_assert(!group->mute_master);
++    pa_assert(!group->volume_master);
++    pa_assert(!group->mute_control);
++    pa_assert(!group->volume_control);
+-    if (group->volume_control_target_info)
+-        pa_binding_target_info_free(group->volume_control_target_info);
++    if (group->audio_group)
++        pa_audio_group_free(group->audio_group);
+-    pa_xfree(group->description);
+-    pa_xfree(group->id);
+     pa_xfree(group);
+ }
+-static void audio_group_set_description(struct audio_group *group, const char *description) {
++static void group_set_own_control_name(struct group *group, enum control_type type, const char *name) {
++    struct group *slave;
++    void *state;
++
+     pa_assert(group);
+-    pa_assert(description);
+-    pa_xfree(group->description);
+-    group->description = pa_xstrdup(description);
++    if (name)
++        group_set_master_name(group, type, NULL);
++
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            if (pa_safe_streq(name, group->own_volume_control_name))
++                return;
++
++            if (group->volume_control) {
++                control_free(group->volume_control);
++                group->volume_control = NULL;
++            }
++
++            pa_xfree(group->own_volume_control_name);
++            group->own_volume_control_name = pa_xstrdup(name);
++
++            if (name) {
++                control_new_for_group(group, CONTROL_TYPE_VOLUME, name, true, &group->volume_control);
++
++                PA_HASHMAP_FOREACH(slave, group->volume_slaves, state) {
++                    if (slave->volume_control)
++                        control_set_master(slave->volume_control, group->volume_control);
++                }
++            }
++            break;
++
++        case CONTROL_TYPE_MUTE:
++            if (pa_safe_streq(name, group->own_mute_control_name))
++                return;
++
++            if (group->mute_control) {
++                control_free(group->mute_control);
++                group->mute_control = NULL;
++            }
++
++            pa_xfree(group->own_mute_control_name);
++            group->own_mute_control_name = pa_xstrdup(name);
++
++            if (name) {
++                control_new_for_group(group, CONTROL_TYPE_MUTE, name, true, &group->mute_control);
++
++                PA_HASHMAP_FOREACH(slave, group->mute_slaves, state) {
++                    if (slave->mute_control)
++                        control_set_master(slave->mute_control, group->mute_control);
++                }
++            }
++            break;
++    }
+ }
+-static void audio_group_set_volume_control_action(struct audio_group *group, enum control_action action,
+-                                                  pa_binding_target_info *target_info) {
++static void group_set_master(struct group *group, enum control_type type, struct group *master) {
++    struct group *old_master;
++    struct control *master_control = NULL;
++
+     pa_assert(group);
+-    pa_assert((action == CONTROL_ACTION_BIND) ^ !target_info);
++    pa_assert(master != group);
++
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            old_master = group->volume_master;
++
++            if (master == old_master)
++                return;
++
++            if (old_master)
++                group_remove_slave(old_master, CONTROL_TYPE_VOLUME, group);
++
++            group->volume_master = master;
++
++            if (master)
++                group_add_slave(master, CONTROL_TYPE_VOLUME, group);
++
++            if (group->volume_control) {
++                if (master)
++                    master_control = master->volume_control;
+-    group->volume_control_action = action;
++                control_set_master(group->volume_control, master_control);
++            }
++            break;
++
++        case CONTROL_TYPE_MUTE:
++            old_master = group->mute_master;
++
++            if (master == old_master)
++                return;
+-    if (group->volume_control_target_info)
+-        pa_binding_target_info_free(group->volume_control_target_info);
++            if (old_master)
++                group_remove_slave(old_master, CONTROL_TYPE_MUTE, group);
+-    if (action == CONTROL_ACTION_BIND)
+-        group->volume_control_target_info = pa_binding_target_info_copy(target_info);
+-    else
+-        group->volume_control_target_info = NULL;
++            group->mute_master = master;
++
++            if (master)
++                group_add_slave(master, CONTROL_TYPE_MUTE, group);
++
++            if (group->mute_control) {
++                if (master)
++                    master_control = master->volume_control;
++
++                control_set_master(group->volume_control, master_control);
++            }
++            break;
++    }
+ }
+-static void audio_group_set_mute_control_action(struct audio_group *group, enum control_action action,
+-                                                pa_binding_target_info *target_info) {
++static int group_set_master_name(struct group *group, enum control_type type, const char *name) {
++    struct group *slave;
++    void *state;
++    struct group *master = NULL;
++
+     pa_assert(group);
+-    pa_assert((action == CONTROL_ACTION_BIND) ^ !target_info);
+-    group->mute_control_action = action;
++    if (pa_safe_streq(name, group->audio_group->name)) {
++        pa_log("Can't bind audio group control to itself.");
++        return -PA_ERR_INVALID;
++    }
+-    if (group->mute_control_target_info)
+-        pa_binding_target_info_free(group->mute_control_target_info);
++    if (name)
++        group_set_own_control_name(group, type, NULL);
+-    if (action == CONTROL_ACTION_BIND)
+-        group->mute_control_target_info = pa_binding_target_info_copy(target_info);
+-    else
+-        group->mute_control_target_info = NULL;
+-}
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            if (pa_safe_streq(name, group->volume_master_name))
++                return 0;
+-static struct stream *stream_new(struct userdata *u, const char *name) {
+-    struct stream *stream;
++            pa_xfree(group->volume_master_name);
++            group->volume_master_name = pa_xstrdup(name);
+-    pa_assert(u);
+-    pa_assert(name);
++            if (name && !group->volume_control) {
++                control_new_for_group(group, CONTROL_TYPE_VOLUME, "audio-group-volume-control", false, &group->volume_control);
+-    stream = pa_xnew0(struct stream, 1);
+-    stream->userdata = u;
+-    stream->id = pa_xstrdup(name);
+-    stream->direction = match_direction_unknown;
++                PA_HASHMAP_FOREACH(slave, group->volume_slaves, state) {
++                    if (slave->volume_control)
++                        control_set_master(slave->volume_control, group->volume_control);
++                }
+-    return stream;
+-}
++            } else if (!name && group->volume_control) {
++                control_free(group->volume_control);
++                group->volume_control = NULL;
++            }
++            break;
+-static void stream_put(struct stream *stream) {
+-    pa_assert(stream);
++        case CONTROL_TYPE_MUTE:
++            if (pa_safe_streq(name, group->mute_master_name))
++                return 0;
+-    if (stream->audio_group_name_for_volume) {
+-        stream->audio_group_for_volume = pa_hashmap_get(stream->userdata->audio_groups, stream->audio_group_name_for_volume);
+-        if (stream->audio_group_for_volume)
+-            stream->volume_control_target_info =
+-                    pa_binding_target_info_new(PA_AUDIO_GROUP_BINDING_TARGET_TYPE, stream->audio_group_for_volume->name,
+-                                               PA_AUDIO_GROUP_BINDING_TARGET_FIELD_VOLUME_CONTROL);
+-        else
+-            pa_log("Stream %s refers to undefined audio group %s.", stream->id, stream->audio_group_name_for_volume);
++            pa_xfree(group->mute_master_name);
++            group->mute_master_name = pa_xstrdup(name);
++
++            if (name && !group->mute_control) {
++                control_new_for_group(group, CONTROL_TYPE_MUTE, "audio-group-mute-control", false, &group->mute_control);
++
++                PA_HASHMAP_FOREACH(slave, group->mute_slaves, state) {
++                    if (slave->mute_control)
++                        control_set_master(slave->mute_control, group->mute_control);
++                }
++
++            } else if (!name && group->mute_control) {
++                control_free(group->mute_control);
++                group->mute_control = NULL;
++            }
++            break;
+     }
+-    if (stream->audio_group_name_for_mute) {
+-        stream->audio_group_for_mute = pa_hashmap_get(stream->userdata->audio_groups, stream->audio_group_name_for_mute);
+-        if (stream->audio_group_for_mute)
+-            stream->mute_control_target_info =
+-                    pa_binding_target_info_new(PA_AUDIO_GROUP_BINDING_TARGET_TYPE, stream->audio_group_for_mute->name,
+-                                               PA_AUDIO_GROUP_BINDING_TARGET_FIELD_MUTE_CONTROL);
+-        else
+-            pa_log("Stream %s refers to undefined audio group %s.", stream->id, stream->audio_group_name_for_volume);
++    if (name)
++        master = pa_hashmap_get(group->userdata->groups, name);
++
++    group_set_master(group, type, master);
++
++    return 0;
++}
++
++static void group_disable_control(struct group *group, enum control_type type) {
++    pa_assert(group);
++
++    group_set_own_control_name(group, type, NULL);
++    group_set_master_name(group, type, NULL);
++}
++
++static void group_add_slave(struct group *group, enum control_type type, struct group *slave) {
++    pa_assert(group);
++    pa_assert(slave);
++
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            pa_assert_se(pa_hashmap_put(group->volume_slaves, slave, slave) >= 0);
++            break;
++
++        case CONTROL_TYPE_MUTE:
++            pa_assert_se(pa_hashmap_put(group->mute_slaves, slave, slave) >= 0);
++            break;
+     }
+ }
+-static void stream_unlink(struct stream *stream) {
+-    pa_assert(stream);
++static void group_remove_slave(struct group *group, enum control_type type, struct group *slave) {
++    pa_assert(group);
++    pa_assert(slave);
+-    if (stream->unlinked)
+-        return;
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            pa_assert_se(pa_hashmap_remove(group->volume_slaves, slave));
++            break;
+-    if (stream->mute_control_target_info) {
+-        pa_binding_target_info_free(stream->mute_control_target_info);
+-        stream->mute_control_target_info = NULL;
++        case CONTROL_TYPE_MUTE:
++            pa_assert_se(pa_hashmap_remove(group->mute_slaves, slave));
+     }
++}
+-    if (stream->volume_control_target_info) {
+-        pa_binding_target_info_free(stream->volume_control_target_info);
+-        stream->volume_control_target_info = NULL;
++static void group_add_stream_rule(struct group *group, enum control_type type, struct stream_rule *rule) {
++    pa_assert(group);
++    pa_assert(rule);
++
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            pa_assert_se(pa_hashmap_put(group->volume_stream_rules, rule, rule) >= 0);
++            break;
++
++        case CONTROL_TYPE_MUTE:
++            pa_assert_se(pa_hashmap_put(group->mute_stream_rules, rule, rule) >= 0);
++            break;
+     }
++}
++
++static void group_remove_stream_rule(struct group *group, enum control_type type, struct stream_rule *rule) {
++    pa_assert(group);
++    pa_assert(rule);
+-    stream->unlinked = true;
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            pa_assert_se(pa_hashmap_remove(group->volume_stream_rules, rule));
++            break;
++
++        case CONTROL_TYPE_MUTE:
++            pa_assert_se(pa_hashmap_remove(group->mute_stream_rules, rule));
++            break;
++    }
+ }
+-static void stream_free(struct stream *stream) {
+-    pa_assert(stream);
++static struct stream_rule *stream_rule_new(struct userdata *u, const char *name) {
++    struct stream_rule *rule;
+-    if (!stream->unlinked)
+-        stream_unlink(stream);
++    pa_assert(u);
++    pa_assert(name);
+-    if (stream->rule)
+-        delete_expression(stream->rule);
++    rule = pa_xnew0(struct stream_rule, 1);
++    rule->userdata = u;
++    rule->name = pa_xstrdup(name);
++    rule->direction = match_direction_unknown;
++    rule->match_expression = expression_new();
+-    pa_xfree(stream->audio_group_name_for_mute);
+-    pa_xfree(stream->audio_group_name_for_volume);
+-    pa_xfree(stream->id);
+-    pa_xfree(stream);
++    return rule;
+ }
+-static void stream_set_audio_group_name_for_volume(struct stream *stream, const char *name) {
+-    pa_assert(stream);
++static void stream_rule_free(struct stream_rule *rule) {
++    pa_assert(rule);
+-    pa_xfree(stream->audio_group_name_for_volume);
+-    stream->audio_group_name_for_volume = pa_xstrdup(name);
++    if (rule->match_expression)
++        expression_free(rule->match_expression);
++
++    stream_rule_set_group_name(rule, CONTROL_TYPE_MUTE, NULL);
++    stream_rule_set_group_name(rule, CONTROL_TYPE_VOLUME, NULL);
++    pa_xfree(rule->name);
++    pa_xfree(rule);
+ }
+-static void stream_set_audio_group_name_for_mute(struct stream *stream, const char *name) {
+-    pa_assert(stream);
++static void stream_rule_set_match_expression(struct stream_rule *rule, struct expression *expression) {
++    pa_assert(rule);
++    pa_assert(expression);
+-    pa_xfree(stream->audio_group_name_for_mute);
+-    stream->audio_group_name_for_mute = pa_xstrdup(name);
++    if (rule->match_expression)
++        expression_free(rule->match_expression);
++
++    rule->match_expression = expression;
+ }
+-/* stream classification */
++static void stream_rule_set_group(struct stream_rule *rule, enum control_type type, struct group *group) {
++    pa_assert(rule);
+-static bool match_predicate(struct literal *l, pas_stream *d) {
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            if (group == rule->group_for_volume)
++                return;
+-    if (l->stream_direction != match_direction_unknown) {
+-        /* check the stream direction; _sink inputs_ are always _outputs_ */
++            if (rule->group_for_volume)
++                group_remove_stream_rule(rule->group_for_volume, CONTROL_TYPE_VOLUME, rule);
+-        if ((d->direction == PA_DIRECTION_OUTPUT && l->stream_direction == match_direction_output) ||
+-            ((d->direction == PA_DIRECTION_INPUT && l->stream_direction == match_direction_input))) {
+-            return true;
+-        }
++            rule->group_for_volume = group;
++
++            if (group)
++                group_add_stream_rule(group, CONTROL_TYPE_VOLUME, rule);
++            break;
++
++        case CONTROL_TYPE_MUTE:
++            if (group == rule->group_for_mute)
++                return;
++
++            if (rule->group_for_mute)
++                group_remove_stream_rule(rule->group_for_mute, CONTROL_TYPE_MUTE, rule);
++
++            rule->group_for_mute = group;
++
++            if (group)
++                group_add_stream_rule(group, CONTROL_TYPE_MUTE, rule);
++            break;
+     }
+-    else if (l->property_name && l->property_value) {
+-        /* check the property from the property list */
++}
+-        if (pa_proplist_contains(d->proplist, l->property_name)) {
+-            const char *prop = pa_proplist_gets(d->proplist, l->property_name);
++static void stream_rule_set_group_name(struct stream_rule *rule, enum control_type type, const char *name) {
++    struct group *group = NULL;
+-            if (prop && strcmp(prop, l->property_value) == 0) {
+-                return true;
+-            }
+-        }
++    pa_assert(rule);
++
++    switch (type) {
++        case CONTROL_TYPE_VOLUME:
++            pa_xfree(rule->audio_group_name_for_volume);
++            rule->audio_group_name_for_volume = pa_xstrdup(name);
++            break;
++
++        case CONTROL_TYPE_MUTE:
++            pa_xfree(rule->audio_group_name_for_mute);
++            rule->audio_group_name_for_mute = pa_xstrdup(name);
++            break;
+     }
+-    /* no match */
+-    return false;
+-}
++    if (name)
++        group = pa_hashmap_get(rule->userdata->groups, name);
+-static bool match_rule(struct expression *e, pas_stream *d) {
++    stream_rule_set_group(rule, type, group);
++}
++static bool stream_rule_match(struct stream_rule *rule, pas_stream *stream) {
+     struct conjunction *c;
+-    PA_LLIST_FOREACH(c, e->conjunctions) {
++    PA_LLIST_FOREACH(c, rule->match_expression->conjunctions) {
+         struct literal *l;
+         bool and_success = true;
+         PA_LLIST_FOREACH(l, c->literals) {
+-            if (!match_predicate(l, d)) {
++            if (!literal_match(l, stream)) {
+                 /* at least one fail for conjunction */
+                 and_success = false;
+                 break;
+@@ -421,56 +1012,246 @@ static bool match_rule(struct expression *e, pas_stream *d) {
+     return false;
+ }
+-static void classify_stream(struct userdata *u, pas_stream *new_data, bool mute) {
+-    /* do the classification here */
++/* stream classification */
+-    struct stream *stream = NULL;
+-    unsigned idx;
++static bool literal_match(struct literal *literal, pas_stream *stream) {
++
++    if (literal->stream_direction != match_direction_unknown) {
++        /* check the stream direction; _sink inputs_ are always _outputs_ */
+-    /* go through the stream match definitions in given order */
++        if ((stream->direction == PA_DIRECTION_OUTPUT && literal->stream_direction == match_direction_output) ||
++            (stream->direction == PA_DIRECTION_INPUT && literal->stream_direction == match_direction_input)) {
++            return literal->negation ? false : true;
++        }
++    }
++    else if (literal->property_name && literal->property_value) {
++        /* check the property from the property list */
+-    PA_DYNARRAY_FOREACH(stream, u->streams, idx) {
+-        if (stream->rule && match_rule(stream->rule, new_data)) {
+-            pa_log_info("stream %s (%s) match with rule %s:", new_data->name, new_data->description, stream->id);
+-            print_expression(stream->rule);
++        if (pa_proplist_contains(stream->proplist, literal->property_name)) {
++            const char *prop = pa_proplist_gets(stream->proplist, literal->property_name);
+-            if (mute) {
+-                if (new_data->use_default_mute_control && stream->audio_group_for_mute)
+-                    pas_stream_bind_mute_control(new_data, stream->mute_control_target_info);
+-            } else {
+-                if (new_data->use_default_volume_control && stream->audio_group_for_volume)
+-                    pas_stream_bind_volume_control(new_data, stream->volume_control_target_info);
+-            }
++            if (prop && strcmp(prop, literal->property_value) == 0)
++                return literal->negation ? false : true;
++        }
++    }
++
++    /* no match */
++    return literal->negation ? true : false;
++}
++
++static pa_hook_result_t stream_put_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pas_stream *stream = call_data;
++    struct stream_rule *rule;
++    unsigned idx;
+-            return;
++    pa_assert(u);
++    pa_assert(stream);
++
++    PA_DYNARRAY_FOREACH(rule, u->stream_rules_list, idx) {
++        if (stream_rule_match(rule, stream)) {
++            pa_hashmap_put(u->rules_by_stream, stream, rule);
++            break;
+         }
+     }
+-    /* no matches, don't touch the volumes */
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t stream_unlink_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pas_stream *stream = call_data;
++
++    pa_assert(u);
++    pa_assert(stream);
++
++    pa_hashmap_remove(u->rules_by_stream, stream);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t volume_control_implementation_initialized_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_volume_control *volume_control = call_data;
++    struct control *control;
++
++    pa_assert(u);
++    pa_assert(volume_control);
++
++    if (volume_control->purpose != PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME)
++        return PA_HOOK_OK;
++
++    control = control_new_for_stream(u, CONTROL_TYPE_VOLUME, volume_control->owner_stream);
++    control_put(control);
++    pa_assert_se(pa_hashmap_put(u->stream_volume_controls, volume_control->owner_stream, control) >= 0);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t mute_control_implementation_initialized_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_mute_control *mute_control = call_data;
++    struct control *control;
++
++    pa_assert(u);
++    pa_assert(mute_control);
++
++    if (mute_control->purpose != PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE)
++        return PA_HOOK_OK;
++
++    control = control_new_for_stream(u, CONTROL_TYPE_MUTE, mute_control->owner_stream);
++    control_put(control);
++    pa_assert_se(pa_hashmap_put(u->stream_mute_controls, mute_control->owner_stream, control) >= 0);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t volume_control_set_initial_volume_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_volume_control *volume_control = call_data;
++    struct stream_rule *rule;
++    struct control *control;
++
++    pa_assert(u);
++    pa_assert(volume_control);
++
++    if (volume_control->purpose != PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME)
++        return PA_HOOK_OK;
++
++    rule = pa_hashmap_get(u->rules_by_stream, volume_control->owner_stream);
++    if (!rule)
++        return PA_HOOK_OK;
++
++    if (!rule->group_for_volume)
++        return PA_HOOK_OK;
++
++    if (!rule->group_for_volume->volume_control)
++        return PA_HOOK_OK;
++
++    control = pa_hashmap_get(u->stream_volume_controls, volume_control->owner_stream);
++    pa_assert(control);
++    pa_assert(control->volume_control == volume_control);
++
++    /* This will set the volume for volume_control. */
++    control_set_master(control, rule->group_for_volume->volume_control);
++
++    return PA_HOOK_STOP;
++}
++
++static pa_hook_result_t mute_control_set_initial_mute_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_mute_control *mute_control = call_data;
++    struct stream_rule *rule;
++    struct control *control;
++
++    pa_assert(u);
++    pa_assert(mute_control);
++
++    if (mute_control->purpose != PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE)
++        return PA_HOOK_OK;
++
++    rule = pa_hashmap_get(u->rules_by_stream, mute_control->owner_stream);
++    if (!rule)
++        return PA_HOOK_OK;
++
++    if (!rule->group_for_mute)
++        return PA_HOOK_OK;
++
++    if (!rule->group_for_mute->mute_control)
++        return PA_HOOK_OK;
++
++    control = pa_hashmap_get(u->stream_mute_controls, mute_control->owner_stream);
++    pa_assert(control);
++    pa_assert(control->mute_control == mute_control);
++
++    /* This will set the mute for mute_control. */
++    control_set_master(control, rule->group_for_mute->mute_control);
++
++    return PA_HOOK_STOP;
++}
++
++static pa_hook_result_t volume_control_volume_changed_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_volume_control *volume_control = call_data;
++    struct control *control;
++
++    pa_assert(u);
++    pa_assert(volume_control);
++
++    if (volume_control->purpose != PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME)
++        return PA_HOOK_OK;
++
++    control = pa_hashmap_get(u->stream_volume_controls, volume_control->owner_stream);
++    if (!control)
++        return PA_HOOK_OK;
++
++    if (!control->master)
++        return PA_HOOK_OK;
++
++    pa_volume_control_set_volume(control->master->volume_control, &volume_control->volume, true, true);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t mute_control_mute_changed_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_mute_control *mute_control = call_data;
++    struct control *control;
++
++    pa_assert(u);
++    pa_assert(mute_control);
++
++    if (mute_control->purpose != PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE)
++        return PA_HOOK_OK;
++
++    control = pa_hashmap_get(u->stream_mute_controls, mute_control->owner_stream);
++    if (!control)
++        return PA_HOOK_OK;
++
++    if (!control->master)
++        return PA_HOOK_OK;
++
++    pa_mute_control_set_mute(control->master->mute_control, mute_control->mute);
++
++    return PA_HOOK_OK;
+ }
+-static pa_hook_result_t set_volume_control_cb(
+-        void *hook_data,
+-        pas_stream *new_data,
+-        struct userdata *u) {
++static pa_hook_result_t volume_control_unlink_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_volume_control *volume_control = call_data;
++    struct control *control;
+-    pa_assert(new_data);
+     pa_assert(u);
++    pa_assert(volume_control);
++
++    if (volume_control->purpose != PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME)
++        return PA_HOOK_OK;
+-    classify_stream(u, new_data, false);
++    control = pa_hashmap_remove(u->stream_volume_controls, volume_control->owner_stream);
++    if (!control)
++        return PA_HOOK_OK;
++
++    control_free(control);
+     return PA_HOOK_OK;
+ }
+-static pa_hook_result_t set_mute_control_cb(
+-        void *hook_data,
+-        pas_stream *new_data,
+-        struct userdata *u) {
++static pa_hook_result_t mute_control_unlink_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_mute_control *mute_control = call_data;
++    struct control *control;
+-    pa_assert(new_data);
+     pa_assert(u);
++    pa_assert(mute_control);
++
++    if (mute_control->purpose != PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE)
++        return PA_HOOK_OK;
+-    classify_stream(u, new_data, true);
++    control = pa_hashmap_remove(u->stream_mute_controls, mute_control->owner_stream);
++    if (!control)
++        return PA_HOOK_OK;
++
++    control_free(control);
+     return PA_HOOK_OK;
+ }
+@@ -520,6 +1301,7 @@ static pa_hook_result_t set_mute_control_cb(
+     (property application.process.binary=paplay OR (direction input OR direction output))
+ */
++#if 0
+ static void print_literal(struct literal *l) {
+     if (l->stream_direction != match_direction_unknown) {
+         pa_log_info("       %sstream direction %s",
+@@ -549,6 +1331,7 @@ static void print_expression(struct expression *e) {
+         print_conjunction(c);
+     }
+ }
++#endif
+ static void delete_literal(struct literal *l) {
+@@ -573,14 +1356,23 @@ static void delete_conjunction(struct conjunction *c) {
+     pa_xfree(c);
+ }
+-static void delete_expression(struct expression *e) {
++static struct expression *expression_new(void) {
++    struct expression *expression;
++
++    expression = pa_xnew0(struct expression, 1);
++
++    return expression;
++}
++
++static void expression_free(struct expression *expression) {
+     struct conjunction *c;
+-    PA_LLIST_FOREACH(c, e->conjunctions) {
++    pa_assert(expression);
++
++    PA_LLIST_FOREACH(c, expression->conjunctions)
+         delete_conjunction(c);
+-    }
+-    pa_xfree(e);
++    pa_xfree(expression);
+ }
+ enum logic_operator {
+@@ -917,26 +1709,21 @@ static bool gather_expression(struct expression *e, struct expression_token *et)
+     return true;
+ }
+-static struct expression *parse_rule(const char *rule_string) {
+-    char *k, *l;
++static int expression_from_string(const char *str, struct expression **_r) {
++    const char *k;
++    char *l;
+     struct expression *e = NULL;
+-    int len;
+     char *buf = NULL;
+     struct expression_token *et = NULL;
+-    if (!rule_string)
+-        goto error;
+-
+-    len = strlen(rule_string);
+-
+-    buf = (char *) pa_xmalloc0(len);
++    pa_assert(str);
++    pa_assert(_r);
+-    if (!buf)
+-        goto error;
++    buf = pa_xmalloc0(strlen(str) + 1);
+     /* remove whitespace */
+-    k = (char *) rule_string;
++    k = str;
+     l = buf;
+     while (*k) {
+@@ -958,9 +1745,6 @@ static struct expression *parse_rule(const char *rule_string) {
+     e = pa_xnew0(struct expression, 1);
+-    if (!e)
+-        goto error;
+-
+     PA_LLIST_HEAD_INIT(struct conjunction, e->conjunctions);
+     /* gather expressions to actual match format */
+@@ -978,30 +1762,15 @@ static struct expression *parse_rule(const char *rule_string) {
+     delete_expression_token(et);
+     pa_xfree(buf);
+-    return e;
++    *_r = e;
++    return 0;
+ error:
+     delete_expression_token(et);
+     pa_xfree(buf);
+-    pa_xfree(e);
+-    return NULL;
+-}
+-
+-static int parse_audio_groups(pa_config_parser_state *state) {
+-    struct userdata *u;
+-    char *name;
+-    const char *split_state = NULL;
+-
+-    pa_assert(state);
+-
+-    u = state->userdata;
+-
+-    pa_hashmap_remove_all(u->audio_group_names);
++    expression_free(e);
+-    while ((name = pa_split_spaces(state->rvalue, &split_state)))
+-        pa_hashmap_put(u->audio_group_names, name, name);
+-
+-    return 0;
++    return -PA_ERR_INVALID;
+ }
+ static int parse_streams(pa_config_parser_state *state) {
+@@ -1013,15 +1782,13 @@ static int parse_streams(pa_config_parser_state *state) {
+     u = state->userdata;
+-    pa_dynarray_remove_all(u->stream_names);
+-
+     while ((name = pa_split_spaces(state->rvalue, &split_state))) {
+         const char *name2;
+         unsigned idx;
+         bool duplicate = false;
+-        /* Avoid adding duplicates in u->stream_names. */
+-        PA_DYNARRAY_FOREACH(name2, u->stream_names, idx) {
++        /* Avoid adding duplicates in u->stream_rule_names. */
++        PA_DYNARRAY_FOREACH(name2, u->stream_rule_names, idx) {
+             if (pa_streq(name, name2)) {
+                 duplicate = true;
+                 break;
+@@ -1033,230 +1800,221 @@ static int parse_streams(pa_config_parser_state *state) {
+             continue;
+         }
+-        pa_dynarray_append(u->stream_names, name);
++        pa_dynarray_append(u->stream_rule_names, name);
+     }
+     return 0;
+ }
+-static int parse_common(pa_config_parser_state *state) {
+-#define AUDIOGROUP_START "AudioGroup "
+-#define STREAM_START "Stream "
+-#define BIND_KEYWORD "bind:"
+-#define NONE_KEYWORD "none"
+-
+-    char *section;
+-    struct userdata *u = (struct userdata *) state->userdata;
+-    int r;
+-    pa_binding_target_info *target_info;
+-
++static int parse_group_control(pa_config_parser_state *state, struct group *group, enum control_type type) {
+     pa_assert(state);
++    pa_assert(group);
+-    section = state->section;
+-    if (!section)
+-        goto error;
++    if (pa_streq(state->rvalue, NONE_KEYWORD))
++        group_disable_control(group, type);
++
++    else if (pa_startswith(state->rvalue, CREATE_PREFIX))
++        group_set_own_control_name(group, type, state->rvalue + strlen(CREATE_PREFIX));
+-    if (strncmp(section, AUDIOGROUP_START, strlen(AUDIOGROUP_START)) == 0) {
+-        char *ag_name = section + strlen(AUDIOGROUP_START);
+-        struct audio_group *ag = (struct audio_group *) pa_hashmap_get(u->unused_audio_groups, ag_name);
++    else if (pa_startswith(state->rvalue, BIND_PREFIX)) {
++        if (pa_startswith(state->rvalue, BIND_AUDIO_GROUP_PREFIX)) {
++            int r;
+-        if (!ag) {
+-            /* first item for this audio group section, so create the struct */
+-            ag = audio_group_new(u, ag_name);
+-            pa_hashmap_put(u->unused_audio_groups, ag->id, ag);
++            r = group_set_master_name(group, type, state->rvalue + strlen(BIND_AUDIO_GROUP_PREFIX));
++            if (r < 0) {
++                pa_log("[%s:%u] Failed to set binding target \"%s\".", state->filename, state->lineno, state->rvalue + strlen(BIND_PREFIX));
++                return r;
++            }
++        } else {
++            pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue + strlen(BIND_PREFIX));
++            return -PA_ERR_INVALID;
+         }
++    } else {
++        pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
++        return -PA_ERR_INVALID;
++    }
+-        if (strcmp(state->lvalue, "description") == 0)
+-            audio_group_set_description(ag, state->rvalue);
++    return 0;
++}
++
++static int parse_common(pa_config_parser_state *state) {
++    char *section;
++    struct userdata *u = state->userdata;
++    const char *name;
++    int r;
+-        else if (strcmp(state->lvalue, "volume-control") == 0) {
+-            if (pa_streq(state->rvalue, "create"))
+-                audio_group_set_volume_control_action(ag, CONTROL_ACTION_CREATE, NULL);
++    pa_assert(state);
+-            else if (pa_streq(state->rvalue, NONE_KEYWORD))
+-                audio_group_set_volume_control_action(ag, CONTROL_ACTION_NONE, NULL);
++    section = state->section;
++    if (!section) {
++        pa_log("[%s:%u] Lvalue \"%s\" not expected in the General section.", state->filename, state->lineno, state->lvalue);
++        return -PA_ERR_INVALID;
++    }
+-            else if (pa_startswith(state->rvalue, BIND_KEYWORD)) {
+-                r = pa_binding_target_info_new_from_string(state->rvalue, "volume_control", &target_info);
+-                if (r < 0) {
+-                    pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue);
+-                    goto error;
+-                }
++    if (pa_startswith(section, AUDIOGROUP_START)) {
++        struct group *group;
+-                audio_group_set_volume_control_action(ag, CONTROL_ACTION_BIND, target_info);
+-                pa_binding_target_info_free(target_info);
++        name = section + strlen(AUDIOGROUP_START);
+-            } else {
+-                pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
+-                goto error;
++        group = pa_hashmap_get(u->groups, name);
++        if (!group) {
++            r = group_new(u, name, &group);
++            if (r < 0) {
++                pa_log("[%s:%u] Failed to create an audio group with name \"%s\".", state->filename, state->lineno, name);
++                return r;
+             }
++
++            pa_hashmap_put(u->groups, (void *) group->audio_group->name, group);
+         }
+-        else if (strcmp(state->lvalue, "mute-control") == 0) {
+-            if (pa_streq(state->rvalue, "create"))
+-                audio_group_set_mute_control_action(ag, CONTROL_ACTION_CREATE, NULL);
+-
+-            else if (pa_streq(state->rvalue, NONE_KEYWORD))
+-                audio_group_set_mute_control_action(ag, CONTROL_ACTION_NONE, NULL);
+-
+-            else if (pa_startswith(state->rvalue, BIND_KEYWORD)) {
+-                r = pa_binding_target_info_new_from_string(state->rvalue, "mute_control", &target_info);
+-                if (r < 0) {
+-                    pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue);
+-                    goto error;
+-                }
+-                audio_group_set_mute_control_action(ag, CONTROL_ACTION_BIND, target_info);
+-                pa_binding_target_info_free(target_info);
++        if (pa_streq(state->lvalue, "description"))
++            pa_audio_group_set_description(group->audio_group, state->rvalue);
+-            } else {
+-                pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
+-                goto error;
+-            }
++        else if (pa_streq(state->lvalue, "volume-control"))
++            return parse_group_control(state, group, CONTROL_TYPE_VOLUME);
++
++        else if (pa_streq(state->lvalue, "mute-control"))
++            return parse_group_control(state, group, CONTROL_TYPE_MUTE);
++
++        else {
++            pa_log("[%s:%u] Lvalue \"%s\" not expected in the AudioGroup section.", state->filename, state->lineno, state->lvalue);
++            return -PA_ERR_INVALID;
+         }
+     }
+-    else if (strncmp(section, STREAM_START, strlen(STREAM_START)) == 0) {
+-        char *stream_name = section + strlen(STREAM_START);
++    else if (pa_startswith(section, STREAM_RULE_START)) {
++        struct stream_rule *rule;
+-        struct stream *stream = (struct stream *) pa_hashmap_get(u->unused_streams, stream_name);
++        name = section + strlen(STREAM_RULE_START);
+-        if (!stream) {
+-            /* first item for this stream section, so create the struct */
+-            stream = stream_new(u, stream_name);
+-            pa_hashmap_put(u->unused_streams, stream->id, stream);
++        rule = pa_hashmap_get(u->stream_rules, name);
++        if (!rule) {
++            rule = stream_rule_new(u, name);
++            pa_hashmap_put(u->stream_rules, rule->name, rule);
+         }
+         if (pa_streq(state->lvalue, "audio-group-for-volume"))
+-            stream_set_audio_group_name_for_volume(stream, *state->rvalue ? state->rvalue : NULL);
++            stream_rule_set_group_name(rule, CONTROL_TYPE_VOLUME, state->rvalue);
+         else if (pa_streq(state->lvalue, "audio-group-for-mute"))
+-            stream_set_audio_group_name_for_mute(stream, *state->rvalue ? state->rvalue : NULL);
++            stream_rule_set_group_name(rule, CONTROL_TYPE_MUTE, state->rvalue);
+-        else if (strcmp(state->lvalue, "match") == 0) {
+-            if (!state->rvalue)
+-                goto error;
++        else if (pa_streq(state->lvalue, "match")) {
++            struct expression *expression;
+-            stream->rule = parse_rule(state->rvalue);
+-
+-            if (!stream->rule) {
+-                goto error;
++            r = expression_from_string(state->rvalue, &expression);
++            if (r < 0) {
++                pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
++                return r;
+             }
++
++            stream_rule_set_match_expression(rule, expression);
+         }
+     }
+     return 0;
+-
+-error:
+-
+-    pa_log_error("failed parsing audio group definition file");
+-    return -1;
+-
+-#undef NONE_KEYWORD
+-#undef AUDIO_GROUP_KEYWORD
+-#undef BIND_KEYWORD
+-#undef STREAM_START
+-#undef AUDIOGROUP_START
+ }
+-static void finalize_config(struct userdata *u) {
+-    const char *group_name;
++int pa__init(pa_module *module) {
++    pa_modargs *ma = NULL;
++    struct userdata *u;
++    FILE *f;
++    char *fn = NULL;
++    struct group *group;
+     void *state;
+-    struct audio_group *group;
+-    const char *stream_name;
++    const char *name;
+     unsigned idx;
+-    struct stream *stream;
+-    pa_assert(u);
+-
+-    PA_HASHMAP_FOREACH(group_name, u->audio_group_names, state) {
+-        int r;
++    pa_assert(module);
+-        group = pa_hashmap_remove(u->unused_audio_groups, group_name);
+-        if (!group)
+-            group = audio_group_new(u, group_name);
+-
+-        r = audio_group_put(group);
+-        if (r < 0) {
+-            pa_log("Failed to create audio group %s.", group_name);
+-            audio_group_free(group);
+-            continue;
+-        }
+-
+-        pa_assert_se(pa_hashmap_put(u->audio_groups, group->id, group) >= 0);
++    if (!(ma = pa_modargs_new(module->argument, valid_modargs))) {
++        pa_log("Failed to parse module arguments");
++        goto fail;
+     }
+-    PA_HASHMAP_FOREACH(group, u->unused_audio_groups, state)
+-        pa_log_debug("Audio group %s is not used.", group->id);
+-
+-    pa_hashmap_free(u->unused_audio_groups);
+-    u->unused_audio_groups = NULL;
+-
+-    pa_hashmap_free(u->audio_group_names);
+-    u->audio_group_names = NULL;
+-
+-    PA_DYNARRAY_FOREACH(stream_name, u->stream_names, idx) {
+-        stream = pa_hashmap_remove(u->unused_streams, stream_name);
+-        if (!stream) {
+-            pa_log("Reference to undefined stream %s, ignoring.", stream_name);
+-            continue;
+-        }
++    u = module->userdata = pa_xnew0(struct userdata, 1);
++    u->volume_api = pa_volume_api_get(module->core);
++    u->groups = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
++                                    (pa_free_cb_t) group_free);
++    u->stream_rules = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
++                                          (pa_free_cb_t) stream_rule_free);
++    u->stream_rules_list = pa_dynarray_new(NULL);
++    u->rules_by_stream = pa_hashmap_new(NULL, NULL);
++    u->stream_volume_controls = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) control_free);
++    u->stream_mute_controls = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) control_free);
++    u->stream_put_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_PUT], PA_HOOK_NORMAL, stream_put_cb,
++                                         u);
++    u->stream_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_UNLINK], PA_HOOK_NORMAL,
++                                            stream_unlink_cb, u);
++    u->volume_control_implementation_initialized_slot =
++            pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_IMPLEMENTATION_INITIALIZED],
++                            PA_HOOK_NORMAL, volume_control_implementation_initialized_cb, u);
++    u->mute_control_implementation_initialized_slot =
++            pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_IMPLEMENTATION_INITIALIZED],
++                            PA_HOOK_NORMAL, mute_control_implementation_initialized_cb, u);
++    u->volume_control_set_initial_volume_slot =
++            pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_SET_INITIAL_VOLUME], PA_HOOK_NORMAL,
++                            volume_control_set_initial_volume_cb, u);
++    u->mute_control_set_initial_mute_slot =
++            pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_SET_INITIAL_MUTE], PA_HOOK_NORMAL,
++                            mute_control_set_initial_mute_cb, u);
++    u->volume_control_volume_changed_slot =
++            pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_VOLUME_CHANGED], PA_HOOK_NORMAL,
++                            volume_control_volume_changed_cb, u);
++    u->mute_control_mute_changed_slot =
++            pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_MUTE_CHANGED], PA_HOOK_NORMAL,
++                            mute_control_mute_changed_cb, u);
++    u->volume_control_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK],
++                                                    PA_HOOK_NORMAL, volume_control_unlink_cb, u);
++    u->mute_control_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK],
++                                                  PA_HOOK_NORMAL, mute_control_unlink_cb, u);
++    u->stream_rule_names = pa_dynarray_new(pa_xfree);
++
++    f = pa_open_config_file(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "audio-groups.conf", "audio-groups.conf", NULL, &fn);
++    if (f) {
++        pa_config_item config_items[] = {
++            { "stream-rules", parse_streams, NULL, "General" },
++            { NULL, parse_common, NULL, NULL },
++            { NULL, NULL, NULL, NULL },
++        };
+-        stream_put(stream);
+-        pa_dynarray_append(u->streams, stream);
++        pa_config_parse(fn, f, config_items, NULL, u);
++        pa_xfree(fn);
++        fn = NULL;
++        fclose(f);
++        f = NULL;
+     }
+-    PA_HASHMAP_FOREACH(stream, u->unused_streams, state)
+-        pa_log_debug("Stream %s is not used.", stream->id);
+-
+-    pa_hashmap_free(u->unused_streams);
+-    u->unused_streams = NULL;
+-
+-    pa_dynarray_free(u->stream_names);
+-    u->stream_names = NULL;
+-}
++    PA_HASHMAP_FOREACH(group, u->groups, state)
++        group_put(group);
+-static bool parse_configuration(struct userdata *u, const char *filename) {
+-    FILE *f;
+-    char *fn = NULL;
++    PA_DYNARRAY_FOREACH(name, u->stream_rule_names, idx) {
++        struct stream_rule *rule;
+-    pa_config_item table[] = {
+-        { "audio-groups", parse_audio_groups, NULL, "General" },
+-        { "streams", parse_streams, NULL, "General" },
+-        { NULL, parse_common, NULL, NULL },
+-        { NULL, NULL, NULL, NULL },
+-    };
++        rule = pa_hashmap_get(u->stream_rules, name);
++        if (rule)
++            pa_dynarray_append(u->stream_rules_list, rule);
++        else
++            pa_log("Non-existent stream rule \"%s\" referenced, ignoring.", name);
++    }
+-    u->audio_group_names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
+-    u->unused_audio_groups = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
+-                                                 (pa_free_cb_t) audio_group_free);
+-    u->stream_names = pa_dynarray_new(pa_xfree);
+-    u->unused_streams = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
+-                                            (pa_free_cb_t) stream_free);
++    pa_dynarray_free(u->stream_rule_names);
++    u->stream_rule_names = NULL;
+-    if (pa_is_path_absolute(filename))
+-        f = pa_open_config_file(filename, NULL, NULL, &fn);
+-    else {
+-        char *sys_conf_file;
++    pa_modargs_free(ma);
+-        sys_conf_file = pa_sprintf_malloc(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "%s", filename);
+-        f = pa_open_config_file(sys_conf_file, filename, NULL, &fn);
+-        pa_xfree(sys_conf_file);
+-    }
++    return 0;
+-    if (f) {
+-        pa_config_parse(fn, f, table, NULL, u);
+-        pa_xfree(fn);
+-        fn = NULL;
+-        fclose(f);
+-        f = NULL;
+-    }
++fail:
++    pa__done(module);
+-    finalize_config(u);
++    if (ma)
++        pa_modargs_free(ma);
+-    return true;
++    return -1;
+ }
+ void pa__done(pa_module *m) {
+-    struct userdata* u;
++    struct userdata *u;
+     pa_assert(m);
+@@ -1265,70 +2023,56 @@ void pa__done(pa_module *m) {
+     if (!u)
+         return;
+-    if (u->new_stream_volume)
+-        pa_hook_slot_free(u->new_stream_volume);
++    if (u->mute_control_unlink_slot)
++        pa_hook_slot_free(u->mute_control_unlink_slot);
+-    if (u->new_stream_mute)
+-        pa_hook_slot_free(u->new_stream_mute);
++    if (u->volume_control_unlink_slot)
++        pa_hook_slot_free(u->volume_control_unlink_slot);
+-    if (u->streams)
+-        pa_dynarray_free(u->streams);
++    if (u->mute_control_mute_changed_slot)
++        pa_hook_slot_free(u->mute_control_mute_changed_slot);
+-    if (u->audio_groups)
+-        pa_hashmap_free(u->audio_groups);
++    if (u->volume_control_volume_changed_slot)
++        pa_hook_slot_free(u->volume_control_volume_changed_slot);
+-    if (u->api)
+-        pa_volume_api_unref(u->api);
++    if (u->mute_control_set_initial_mute_slot)
++        pa_hook_slot_free(u->mute_control_set_initial_mute_slot);
+-    pa_xfree(u);
+-}
++    if (u->volume_control_set_initial_volume_slot)
++        pa_hook_slot_free(u->volume_control_set_initial_volume_slot);
+-int pa__init(pa_module *m) {
+-    pa_modargs *ma = NULL;
+-    struct userdata *u;
+-    const char *filename;
++    if (u->mute_control_implementation_initialized_slot)
++        pa_hook_slot_free(u->mute_control_implementation_initialized_slot);
+-    pa_assert(m);
++    if (u->volume_control_implementation_initialized_slot)
++        pa_hook_slot_free(u->volume_control_implementation_initialized_slot);
+-    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+-        pa_log("Failed to parse module arguments");
+-        goto error;
+-    }
++    if (u->stream_unlink_slot)
++        pa_hook_slot_free(u->stream_unlink_slot);
+-    u = m->userdata = pa_xnew0(struct userdata, 1);
++    if (u->stream_put_slot)
++        pa_hook_slot_free(u->stream_put_slot);
+-    if (!u)
+-        goto error;
++    if (u->stream_mute_controls)
++        pa_hashmap_free(u->stream_mute_controls);
+-    u->api = pa_volume_api_get(m->core);
++    if (u->stream_volume_controls)
++        pa_hashmap_free(u->stream_volume_controls);
+-    if (!u->api)
+-        goto error;
+-
+-    u->audio_groups = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
+-                                          (pa_free_cb_t) audio_group_free);
+-    u->streams = pa_dynarray_new((pa_free_cb_t) stream_free);
+-
+-    filename = pa_modargs_get_value(ma, "filename", AUDIO_GROUP_CONFIG);
+-
+-    if (!parse_configuration(u, filename))
+-        goto error;
++    if (u->rules_by_stream)
++        pa_hashmap_free(u->rules_by_stream);
+-    u->new_stream_volume = pa_hook_connect(&u->api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_VOLUME_CONTROL], PA_HOOK_EARLY, (pa_hook_cb_t) set_volume_control_cb, u);
+-    u->new_stream_mute = pa_hook_connect(&u->api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_MUTE_CONTROL], PA_HOOK_EARLY, (pa_hook_cb_t) set_mute_control_cb, u);
++    if (u->stream_rules_list)
++        pa_dynarray_free(u->stream_rules_list);
+-    if (!u->new_stream_volume || !u->new_stream_mute)
+-        goto error;
++    if (u->stream_rules)
++        pa_hashmap_free(u->stream_rules);
+-    pa_modargs_free(ma);
++    if (u->groups)
++        pa_hashmap_free(u->groups);
+-    return 0;
++    if (u->volume_api)
++        pa_volume_api_unref(u->volume_api);
+-error:
+-    pa__done(m);
+-
+-    if (ma)
+-        pa_modargs_free(ma);
+-
+-    return -1;
++    pa_xfree(u);
+ }
+diff --git a/src/modules/main-volume-policy/main-volume-context.c b/src/modules/main-volume-policy/main-volume-context.c
+index 7ac35c6..9b9f9fd 100644
+--- a/src/modules/main-volume-policy/main-volume-context.c
++++ b/src/modules/main-volume-policy/main-volume-context.c
+@@ -28,32 +28,33 @@
+ #include <modules/volume-api/mute-control.h>
+ #include <modules/volume-api/volume-control.h>
+-int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, const char *description,
+-                               pa_main_volume_context **context) {
+-    pa_main_volume_context *context_local;
++#include <pulsecore/core-util.h>
++
++int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, void *userdata, pa_main_volume_context **_r) {
++    pa_main_volume_context *context;
+     int r;
+     pa_assert(policy);
+     pa_assert(name);
+-    pa_assert(description);
+-    pa_assert(context);
++    pa_assert(_r);
+-    context_local = pa_xnew0(struct pa_main_volume_context, 1);
+-    context_local->main_volume_policy = policy;
+-    context_local->index = pa_main_volume_policy_allocate_main_volume_context_index(policy);
++    context = pa_xnew0(struct pa_main_volume_context, 1);
++    context->main_volume_policy = policy;
++    context->index = pa_main_volume_policy_allocate_main_volume_context_index(policy);
+-    r = pa_main_volume_policy_register_name(policy, name, true, &context_local->name);
++    r = pa_main_volume_policy_register_name(policy, name, true, &context->name);
+     if (r < 0)
+         goto fail;
+-    context_local->description = pa_xstrdup(description);
+-
+-    *context = context_local;
++    context->description = pa_xstrdup(context->name);
++    context->userdata = userdata;
++    *_r = context;
+     return 0;
+ fail:
+-    pa_main_volume_context_free(context_local);
++    if (context)
++        pa_main_volume_context_free(context);
+     return r;
+ }
+@@ -62,7 +63,6 @@ void pa_main_volume_context_put(pa_main_volume_context *context) {
+     pa_assert(context);
+     pa_main_volume_policy_add_main_volume_context(context->main_volume_policy, context);
+-
+     context->linked = true;
+     pa_log_debug("Created main volume context #%u.", context->index);
+@@ -93,40 +93,21 @@ void pa_main_volume_context_unlink(pa_main_volume_context *context) {
+     pa_log_debug("Unlinking main volume context %s.", context->name);
+     if (context->linked)
+-        pa_hook_fire(&context->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK], context);
+-
+-    if (context->main_input_mute_control_binding) {
+-        pa_binding_free(context->main_input_mute_control_binding);
+-        context->main_input_mute_control_binding = NULL;
+-    }
++        pa_main_volume_policy_remove_main_volume_context(context->main_volume_policy, context);
+-    if (context->main_output_mute_control_binding) {
+-        pa_binding_free(context->main_output_mute_control_binding);
+-        context->main_output_mute_control_binding = NULL;
+-    }
+-
+-    if (context->main_input_volume_control_binding) {
+-        pa_binding_free(context->main_input_volume_control_binding);
+-        context->main_input_volume_control_binding = NULL;
+-    }
+-
+-    if (context->main_output_volume_control_binding) {
+-        pa_binding_free(context->main_output_volume_control_binding);
+-        context->main_output_volume_control_binding = NULL;
+-    }
++    pa_hook_fire(&context->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK], context);
+     context->main_input_mute_control = NULL;
+     context->main_output_mute_control = NULL;
+     context->main_input_volume_control = NULL;
+     context->main_output_volume_control = NULL;
+-
+-    pa_main_volume_policy_remove_main_volume_context(context->main_volume_policy, context);
+ }
+ void pa_main_volume_context_free(pa_main_volume_context *context) {
+     pa_assert(context);
+-    if (!context->unlinked)
++    /* unlink() expects name to be set. */
++    if (!context->unlinked && context->name)
+         pa_main_volume_context_unlink(context);
+     pa_xfree(context->description);
+@@ -137,13 +118,33 @@ void pa_main_volume_context_free(pa_main_volume_context *context) {
+     pa_xfree(context);
+ }
+-const char *pa_main_volume_context_get_name(pa_main_volume_context *context) {
++void pa_main_volume_context_set_description(pa_main_volume_context *context, const char *description) {
++    char *old_description;
++
+     pa_assert(context);
++    pa_assert(description);
++
++    old_description = context->description;
++
++    if (pa_streq(description, old_description))
++        return;
+-    return context->name;
++    context->description = pa_xstrdup(description);
++
++    if (!context->linked || context->unlinked) {
++        pa_xfree(old_description);
++        return;
++    }
++
++    pa_log_debug("Main volume context %s description changed from \"%s\" to \"%s\".", context->name, old_description,
++                 description);
++    pa_xfree(old_description);
++
++    pa_hook_fire(&context->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_DESCRIPTION_CHANGED],
++                 context);
+ }
+-static void set_main_output_volume_control_internal(pa_main_volume_context *context, pa_volume_control *control) {
++void pa_main_volume_context_set_main_output_volume_control(pa_main_volume_context *context, pa_volume_control *control) {
+     pa_volume_control *old_control;
+     pa_assert(context);
+@@ -158,7 +159,7 @@ static void set_main_output_volume_control_internal(pa_main_volume_context *cont
+     if (!context->linked || context->unlinked)
+         return;
+-    pa_log_debug("The main output volume control of main volume context %s changed from %s to %s.", context->name,
++    pa_log_debug("Main volume context %s main output volume control changed from %s to %s.", context->name,
+                  old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
+     pa_hook_fire(&context->main_volume_policy->hooks
+@@ -166,24 +167,7 @@ static void set_main_output_volume_control_internal(pa_main_volume_context *cont
+                  context);
+ }
+-void pa_main_volume_context_bind_main_output_volume_control(pa_main_volume_context *context,
+-                                                            pa_binding_target_info *target_info) {
+-    pa_binding_owner_info owner_info = {
+-        .userdata = context,
+-        .set_value = (pa_binding_set_value_cb_t) set_main_output_volume_control_internal,
+-    };
+-
+-    pa_assert(context);
+-    pa_assert(target_info);
+-
+-    if (context->main_output_volume_control_binding)
+-        pa_binding_free(context->main_output_volume_control_binding);
+-
+-    context->main_output_volume_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
+-                                                                 target_info);
+-}
+-
+-static void set_main_input_volume_control_internal(pa_main_volume_context *context, pa_volume_control *control) {
++void pa_main_volume_context_set_main_input_volume_control(pa_main_volume_context *context, pa_volume_control *control) {
+     pa_volume_control *old_control;
+     pa_assert(context);
+@@ -198,7 +182,7 @@ static void set_main_input_volume_control_internal(pa_main_volume_context *conte
+     if (!context->linked || context->unlinked)
+         return;
+-    pa_log_debug("The main input volume control of main volume context %s changed from %s to %s.", context->name,
++    pa_log_debug("Main volume context %s main input volume control changed from %s to %s.", context->name,
+                  old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
+     pa_hook_fire(&context->main_volume_policy->hooks
+@@ -206,24 +190,7 @@ static void set_main_input_volume_control_internal(pa_main_volume_context *conte
+                  context);
+ }
+-void pa_main_volume_context_bind_main_input_volume_control(pa_main_volume_context *context,
+-                                                           pa_binding_target_info *target_info) {
+-    pa_binding_owner_info owner_info = {
+-        .userdata = context,
+-        .set_value = (pa_binding_set_value_cb_t) set_main_input_volume_control_internal,
+-    };
+-
+-    pa_assert(context);
+-    pa_assert(target_info);
+-
+-    if (context->main_input_volume_control_binding)
+-        pa_binding_free(context->main_input_volume_control_binding);
+-
+-    context->main_input_volume_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
+-                                                                target_info);
+-}
+-
+-static void set_main_output_mute_control_internal(pa_main_volume_context *context, pa_mute_control *control) {
++void pa_main_volume_context_set_main_output_mute_control(pa_main_volume_context *context, pa_mute_control *control) {
+     pa_mute_control *old_control;
+     pa_assert(context);
+@@ -238,7 +205,7 @@ static void set_main_output_mute_control_internal(pa_main_volume_context *contex
+     if (!context->linked || context->unlinked)
+         return;
+-    pa_log_debug("The main output mute control of main volume context %s changed from %s to %s.", context->name,
++    pa_log_debug("Main volume context %s main output mute control changed from %s to %s.", context->name,
+                  old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
+     pa_hook_fire(&context->main_volume_policy->hooks
+@@ -246,24 +213,7 @@ static void set_main_output_mute_control_internal(pa_main_volume_context *contex
+                  context);
+ }
+-void pa_main_volume_context_bind_main_output_mute_control(pa_main_volume_context *context,
+-                                                          pa_binding_target_info *target_info) {
+-    pa_binding_owner_info owner_info = {
+-        .userdata = context,
+-        .set_value = (pa_binding_set_value_cb_t) set_main_output_mute_control_internal,
+-    };
+-
+-    pa_assert(context);
+-    pa_assert(target_info);
+-
+-    if (context->main_output_mute_control_binding)
+-        pa_binding_free(context->main_output_mute_control_binding);
+-
+-    context->main_output_mute_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
+-                                                               target_info);
+-}
+-
+-static void set_main_input_mute_control_internal(pa_main_volume_context *context, pa_mute_control *control) {
++void pa_main_volume_context_set_main_input_mute_control(pa_main_volume_context *context, pa_mute_control *control) {
+     pa_mute_control *old_control;
+     pa_assert(context);
+@@ -278,48 +228,10 @@ static void set_main_input_mute_control_internal(pa_main_volume_context *context
+     if (!context->linked || context->unlinked)
+         return;
+-    pa_log_debug("The main input mute control of main volume context %s changed from %s to %s.", context->name,
++    pa_log_debug("Main volume context %s main input mute control changed from %s to %s.", context->name,
+                  old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
+     pa_hook_fire(&context->main_volume_policy->hooks
+                      [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_MUTE_CONTROL_CHANGED],
+                  context);
+ }
+-
+-void pa_main_volume_context_bind_main_input_mute_control(pa_main_volume_context *context,
+-                                                         pa_binding_target_info *target_info) {
+-    pa_binding_owner_info owner_info = {
+-        .userdata = context,
+-        .set_value = (pa_binding_set_value_cb_t) set_main_input_mute_control_internal,
+-    };
+-
+-    pa_assert(context);
+-    pa_assert(target_info);
+-
+-    if (context->main_input_mute_control_binding)
+-        pa_binding_free(context->main_input_mute_control_binding);
+-
+-    context->main_input_mute_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
+-                                                              target_info);
+-}
+-
+-pa_binding_target_type *pa_main_volume_context_create_binding_target_type(pa_main_volume_policy *policy) {
+-    pa_binding_target_type *type;
+-
+-    pa_assert(policy);
+-
+-    type = pa_binding_target_type_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, policy->main_volume_contexts,
+-                                      &policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_PUT],
+-                                      &policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK],
+-                                      (pa_binding_target_type_get_name_cb_t) pa_main_volume_context_get_name);
+-    pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL,
+-                                     PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_output_volume_control));
+-    pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL,
+-                                     PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_input_volume_control));
+-    pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL,
+-                                     PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_output_mute_control));
+-    pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL,
+-                                     PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_input_mute_control));
+-
+-    return type;
+-}
+diff --git a/src/modules/main-volume-policy/main-volume-context.h b/src/modules/main-volume-policy/main-volume-context.h
+index 4a0a6f7..3770168 100644
+--- a/src/modules/main-volume-policy/main-volume-context.h
++++ b/src/modules/main-volume-policy/main-volume-context.h
+@@ -24,16 +24,8 @@
+ #include <modules/main-volume-policy/main-volume-policy.h>
+-#include <modules/volume-api/binding.h>
+-
+ typedef struct pa_main_volume_context pa_main_volume_context;
+-#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE "MainVolumeContext"
+-#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL "main_output_volume_control"
+-#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL "main_input_volume_control"
+-#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL "main_output_mute_control"
+-#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL "main_input_mute_control"
+-
+ struct pa_main_volume_context {
+     pa_main_volume_policy *main_volume_policy;
+     uint32_t index;
+@@ -44,32 +36,21 @@ struct pa_main_volume_context {
+     pa_mute_control *main_output_mute_control;
+     pa_mute_control *main_input_mute_control;
+-    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;
+-
+     bool linked;
+     bool unlinked;
++
++    void *userdata;
+ };
+-int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, const char *description,
+-                               pa_main_volume_context **context);
++int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, void *userdata, pa_main_volume_context **_r);
+ void pa_main_volume_context_put(pa_main_volume_context *context);
+ void pa_main_volume_context_unlink(pa_main_volume_context *context);
+ void pa_main_volume_context_free(pa_main_volume_context *context);
+-const char *pa_main_volume_context_get_name(pa_main_volume_context *context);
+-
+-void pa_main_volume_context_bind_main_output_volume_control(pa_main_volume_context *context,
+-                                                            pa_binding_target_info *target_info);
+-void pa_main_volume_context_bind_main_input_volume_control(pa_main_volume_context *context,
+-                                                           pa_binding_target_info *target_info);
+-void pa_main_volume_context_bind_main_output_mute_control(pa_main_volume_context *context,
+-                                                          pa_binding_target_info *target_info);
+-void pa_main_volume_context_bind_main_input_mute_control(pa_main_volume_context *context, pa_binding_target_info *target_info);
+-
+-/* Called from main-volume-policy.c only. */
+-pa_binding_target_type *pa_main_volume_context_create_binding_target_type(pa_main_volume_policy *policy);
++void pa_main_volume_context_set_description(pa_main_volume_context *context, const char *description);
++void pa_main_volume_context_set_main_output_volume_control(pa_main_volume_context *context, pa_volume_control *control);
++void pa_main_volume_context_set_main_input_volume_control(pa_main_volume_context *context, pa_volume_control *control);
++void pa_main_volume_context_set_main_output_mute_control(pa_main_volume_context *context, pa_mute_control *control);
++void pa_main_volume_context_set_main_input_mute_control(pa_main_volume_context *context, pa_mute_control *control);
+ #endif
+diff --git a/src/modules/main-volume-policy/main-volume-policy.c b/src/modules/main-volume-policy/main-volume-policy.c
+index b0b4ede..3c0fccf 100644
+--- a/src/modules/main-volume-policy/main-volume-policy.c
++++ b/src/modules/main-volume-policy/main-volume-policy.c
+@@ -28,6 +28,7 @@
+ #include <modules/main-volume-policy/main-volume-context.h>
+ #include <pulsecore/core-util.h>
++#include <pulsecore/namereg.h>
+ #include <pulsecore/shared.h>
+ static pa_main_volume_policy *main_volume_policy_new(pa_core *core);
+@@ -70,6 +71,46 @@ void pa_main_volume_policy_unref(pa_main_volume_policy *policy) {
+     }
+ }
++static pa_hook_result_t volume_control_unlink_cb(void *hook_data, void *call_data, void *userdata) {
++    pa_main_volume_policy *policy = userdata;
++    pa_volume_control *control = call_data;
++    pa_main_volume_context *context;
++    void *state;
++
++    pa_assert(policy);
++    pa_assert(control);
++
++    PA_HASHMAP_FOREACH(context, policy->main_volume_contexts, state) {
++        if (context->main_output_volume_control == control)
++            pa_main_volume_context_set_main_output_volume_control(context, NULL);
++
++        if (context->main_input_volume_control == control)
++            pa_main_volume_context_set_main_input_volume_control(context, NULL);
++    }
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t mute_control_unlink_cb(void *hook_data, void *call_data, void *userdata) {
++    pa_main_volume_policy *policy = userdata;
++    pa_mute_control *control = call_data;
++    pa_main_volume_context *context;
++    void *state;
++
++    pa_assert(policy);
++    pa_assert(control);
++
++    PA_HASHMAP_FOREACH(context, policy->main_volume_contexts, state) {
++        if (context->main_output_mute_control == control)
++            pa_main_volume_context_set_main_output_mute_control(context, NULL);
++
++        if (context->main_input_mute_control == control)
++            pa_main_volume_context_set_main_input_mute_control(context, NULL);
++    }
++
++    return PA_HOOK_OK;
++}
++
+ static pa_main_volume_policy *main_volume_policy_new(pa_core *core) {
+     pa_main_volume_policy *policy;
+     unsigned i;
+@@ -86,8 +127,10 @@ static pa_main_volume_policy *main_volume_policy_new(pa_core *core) {
+     for (i = 0; i < PA_MAIN_VOLUME_POLICY_HOOK_MAX; i++)
+         pa_hook_init(&policy->hooks[i], policy);
+-    policy->main_volume_context_binding_target_type = pa_main_volume_context_create_binding_target_type(policy);
+-    pa_volume_api_add_binding_target_type(policy->volume_api, policy->main_volume_context_binding_target_type);
++    policy->volume_control_unlink_slot = pa_hook_connect(&policy->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK],
++                                                         PA_HOOK_NORMAL, volume_control_unlink_cb, policy);
++    policy->mute_control_unlink_slot = pa_hook_connect(&policy->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK],
++                                                       PA_HOOK_NORMAL, mute_control_unlink_cb, policy);
+     pa_log_debug("Created a pa_main_volume_policy object.");
+@@ -102,10 +145,11 @@ static void main_volume_policy_free(pa_main_volume_policy *policy) {
+     pa_log_debug("Freeing the pa_main_volume_policy object.");
+-    if (policy->main_volume_context_binding_target_type) {
+-        pa_volume_api_remove_binding_target_type(policy->volume_api, policy->main_volume_context_binding_target_type);
+-        pa_binding_target_type_free(policy->main_volume_context_binding_target_type);
+-    }
++    if (policy->mute_control_unlink_slot)
++        pa_hook_slot_free(policy->mute_control_unlink_slot);
++
++    if (policy->volume_control_unlink_slot)
++        pa_hook_slot_free(policy->volume_control_unlink_slot);
+     for (i = 0; i < PA_MAIN_VOLUME_POLICY_HOOK_MAX; i++)
+         pa_hook_done(&policy->hooks[i]);
+@@ -134,19 +178,24 @@ int pa_main_volume_policy_register_name(pa_main_volume_policy *policy, const cha
+     pa_assert(requested_name);
+     pa_assert(registered_name);
++    if (!pa_namereg_is_valid_name(requested_name)) {
++        pa_log("Invalid name: \"%s\"", requested_name);
++        return -PA_ERR_INVALID;
++    }
++
+     n = pa_xstrdup(requested_name);
+     if (pa_hashmap_put(policy->names, n, n) < 0) {
+         unsigned i = 1;
+-        pa_xfree(n);
+-
+         if (fail_if_already_registered) {
++            pa_xfree(n);
+             pa_log("Name %s already registered.", requested_name);
+             return -PA_ERR_EXIST;
+         }
+         do {
++            pa_xfree(n);
+             i++;
+             n = pa_sprintf_malloc("%s.%u", requested_name, i);
+         } while (pa_hashmap_put(policy->names, n, n) < 0);
+diff --git a/src/modules/main-volume-policy/main-volume-policy.conf.example b/src/modules/main-volume-policy/main-volume-policy.conf.example
+index a4a35d3..3fcd267 100644
+--- a/src/modules/main-volume-policy/main-volume-policy.conf.example
++++ b/src/modules/main-volume-policy/main-volume-policy.conf.example
+@@ -3,7 +3,6 @@ output-volume-model = by-active-main-volume-context
+ input-volume-model = by-active-main-volume-context
+ output-mute-model = none
+ input-mute-model = none
+-main-volume-contexts = x-example-call-main-volume-context x-example-default-main-volume-context
+ [MainVolumeContext x-example-call-main-volume-context]
+ description = Call main volume context
+diff --git a/src/modules/main-volume-policy/main-volume-policy.h b/src/modules/main-volume-policy/main-volume-policy.h
+index 5cd669e..d5f6e02 100644
+--- a/src/modules/main-volume-policy/main-volume-policy.h
++++ b/src/modules/main-volume-policy/main-volume-policy.h
+@@ -22,7 +22,6 @@
+   USA.
+ ***/
+-#include <modules/volume-api/binding.h>
+ #include <modules/volume-api/volume-api.h>
+ #include <pulsecore/core.h>
+@@ -35,6 +34,7 @@ typedef struct pa_main_volume_context pa_main_volume_context;
+ enum {
+     PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_PUT,
+     PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK,
++    PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_DESCRIPTION_CHANGED,
+     PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED,
+     PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_VOLUME_CONTROL_CHANGED,
+     PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_MUTE_CONTROL_CHANGED,
+@@ -53,7 +53,9 @@ struct pa_main_volume_policy {
+     uint32_t next_main_volume_context_index;
+     pa_hook hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAX];
+-    pa_binding_target_type *main_volume_context_binding_target_type;
++
++    pa_hook_slot *volume_control_unlink_slot;
++    pa_hook_slot *mute_control_unlink_slot;
+ };
+ pa_main_volume_policy *pa_main_volume_policy_get(pa_core *core);
+diff --git a/src/modules/main-volume-policy/module-main-volume-policy.c b/src/modules/main-volume-policy/module-main-volume-policy.c
+index 0a89aa7..1b7693e 100644
+--- a/src/modules/main-volume-policy/module-main-volume-policy.c
++++ b/src/modules/main-volume-policy/module-main-volume-policy.c
+@@ -27,7 +27,7 @@
+ #include <modules/main-volume-policy/main-volume-context.h>
+-#include <modules/volume-api/binding.h>
++#include <modules/volume-api/audio-group.h>
+ #include <modules/volume-api/volume-api.h>
+ #include <pulse/direction.h>
+@@ -36,6 +36,9 @@
+ #include <pulsecore/core-util.h>
+ #include <pulsecore/i18n.h>
++#define BIND_PREFIX "bind:"
++#define BIND_AUDIO_GROUP_PREFIX BIND_PREFIX "AudioGroup:"
++
+ PA_MODULE_AUTHOR("Tanu Kaskinen");
+ PA_MODULE_DESCRIPTION(_("Main volume and mute policy"));
+ PA_MODULE_VERSION(PACKAGE_VERSION);
+@@ -52,6 +55,7 @@ enum model {
+ };
+ struct userdata {
++    pa_volume_api *volume_api;
+     pa_main_volume_policy *main_volume_policy;
+     enum model output_volume_model;
+     enum model input_volume_model;
+@@ -60,26 +64,67 @@ struct userdata {
+     pa_hashmap *contexts; /* name -> struct context */
+     pa_hook_slot *active_main_volume_context_changed_slot;
++    pa_hook_slot *main_volume_context_main_output_volume_control_changed_slot;
++    pa_hook_slot *main_volume_context_main_input_volume_control_changed_slot;
++    pa_hook_slot *main_volume_context_main_output_mute_control_changed_slot;
++    pa_hook_slot *main_volume_context_main_input_mute_control_changed_slot;
++    pa_hook_slot *audio_group_put_slot;
++    pa_hook_slot *audio_group_unlink_slot;
++    pa_hook_slot *audio_group_volume_control_changed_slot;
++    pa_hook_slot *audio_group_mute_control_changed_slot;
++};
+-    /* The following fields are only used during initialization. */
+-    pa_hashmap *context_names; /* name -> name (hashmap-as-a-set) */
+-    pa_hashmap *unused_contexts; /* name -> struct context */
++struct control_info {
++    /* As appropriate for this control, points to one of
++     *  - pa_main_volume_context.main_output_volume_control
++     *  - pa_main_volume_context.main_input_volume_control
++     *  - pa_main_volume_context.main_output_mute_control
++     *  - pa_main_volume_context.main_input_mute_control */
++    void **control;
++
++    /* As appropriate for this control, points to one of
++     *  - userdata.output_volume_model
++     *  - userdata.input_volume_model
++     *  - userdata.output_mute_model
++     *  - userdata.input_mute_model */
++    enum model *model;
++
++    /* Name of the audio group to which the context volume or mute control is
++     * bound. If the context control is not bound to anything, this is NULL. */
++    char *binding_target_name;
++
++    /* Points to the audio group to which the context volume or mute control is
++     * bound. If the context control is not bound to anything, or it's bound
++     * but the target doesn't currently exist, this is NULL. */
++    pa_audio_group *binding_target;
++
++    /* As appropriate for this control, points to one of
++     *  - pa_main_volume_context_set_main_output_volume_control()
++     *  - pa_main_volume_context_set_main_input_volume_control()
++     *  - pa_main_volume_context_set_main_output_mute_control()
++     *  - pa_main_volume_context_set_main_input_mute_control() */
++    void (*set_control)(pa_main_volume_context *context, void *control);
++
++    /* As appropriate for this control, points to one of
++     *  - pa_volume_api_set_main_output_volume_control()
++     *  - pa_volume_api_set_main_input_volume_control()
++     *  - pa_volume_api_set_main_output_mute_control()
++     *  - pa_volume_api_set_main_input_mute_control() */
++    void (*set_volume_api_control)(pa_volume_api *api, void *control);
+ };
+ struct context {
+     struct userdata *userdata;
+-    char *name;
+-    char *description;
+-    pa_binding_target_info *main_output_volume_control_target_info;
+-    pa_binding_target_info *main_input_volume_control_target_info;
+-    pa_binding_target_info *main_output_mute_control_target_info;
+-    pa_binding_target_info *main_input_mute_control_target_info;
+     pa_main_volume_context *main_volume_context;
++    struct control_info output_volume_info;
++    struct control_info input_volume_info;
++    struct control_info output_mute_info;
++    struct control_info input_mute_info;
+     bool unlinked;
+ };
+-static void context_unlink(struct context *context);
++static void context_free(struct context *context);
+ static const char *model_to_string(enum model model) {
+     switch (model) {
+@@ -107,56 +152,57 @@ static int model_from_string(const char *str, enum model *model) {
+     return 0;
+ }
+-static struct context *context_new(struct userdata *u, const char *name) {
+-    struct context *context;
++static int context_new(struct userdata *u, const char *name, struct context **_r) {
++    struct context *context = NULL;
++    int r;
+     pa_assert(u);
+     pa_assert(name);
++    pa_assert(_r);
+     context = pa_xnew0(struct context, 1);
+     context->userdata = u;
+-    context->name = pa_xstrdup(name);
+-    context->description = pa_xstrdup(name);
+-    return context;
+-}
+-
+-static int context_put(struct context *context) {
+-    int r;
+-
+-    pa_assert(context);
+-
+-    r = pa_main_volume_context_new(context->userdata->main_volume_policy, context->name, context->description,
+-                                   &context->main_volume_context);
++    r = pa_main_volume_context_new(u->main_volume_policy, name, u, &context->main_volume_context);
+     if (r < 0)
+         goto fail;
+-    if (context->main_output_volume_control_target_info)
+-        pa_main_volume_context_bind_main_output_volume_control(context->main_volume_context,
+-                                                               context->main_output_volume_control_target_info);
++    context->output_volume_info.control = (void **) &context->main_volume_context->main_output_volume_control;
++    context->input_volume_info.control = (void **) &context->main_volume_context->main_input_volume_control;
++    context->output_mute_info.control = (void **) &context->main_volume_context->main_output_mute_control;
++    context->input_mute_info.control = (void **) &context->main_volume_context->main_input_mute_control;
+-    if (context->main_input_volume_control_target_info)
+-        pa_main_volume_context_bind_main_input_volume_control(context->main_volume_context,
+-                                                              context->main_input_volume_control_target_info);
++    context->output_volume_info.model = &u->output_volume_model;
++    context->input_volume_info.model = &u->input_volume_model;
++    context->output_mute_info.model = &u->output_mute_model;
++    context->input_mute_info.model = &u->input_mute_model;
+-    if (context->main_output_mute_control_target_info)
+-        pa_main_volume_context_bind_main_output_mute_control(context->main_volume_context,
+-                                                             context->main_output_mute_control_target_info);
++    context->output_volume_info.set_control = (void *) pa_main_volume_context_set_main_output_volume_control;
++    context->input_volume_info.set_control = (void *) pa_main_volume_context_set_main_input_volume_control;
++    context->output_mute_info.set_control = (void *) pa_main_volume_context_set_main_output_mute_control;
++    context->input_mute_info.set_control = (void *) pa_main_volume_context_set_main_input_mute_control;
+-    if (context->main_input_mute_control_target_info)
+-        pa_main_volume_context_bind_main_input_mute_control(context->main_volume_context,
+-                                                            context->main_input_mute_control_target_info);
+-
+-    pa_main_volume_context_put(context->main_volume_context);
++    context->output_volume_info.set_volume_api_control = (void *) pa_volume_api_set_main_output_volume_control;
++    context->input_volume_info.set_volume_api_control = (void *) pa_volume_api_set_main_input_volume_control;
++    context->output_mute_info.set_volume_api_control = (void *) pa_volume_api_set_main_output_mute_control;
++    context->input_mute_info.set_volume_api_control = (void *) pa_volume_api_set_main_input_mute_control;
++    *_r = context;
+     return 0;
+ fail:
+-    context_unlink(context);
++    if (context)
++        context_free(context);
+     return r;
+ }
++static void context_put(struct context *context) {
++    pa_assert(context);
++
++    pa_main_volume_context_put(context->main_volume_context);
++}
++
+ static void context_unlink(struct context *context) {
+     pa_assert(context);
+@@ -165,10 +211,8 @@ static void context_unlink(struct context *context) {
+     context->unlinked = true;
+-    if (context->main_volume_context) {
+-        pa_main_volume_context_free(context->main_volume_context);
+-        context->main_volume_context = NULL;
+-    }
++    if (context->main_volume_context)
++        pa_main_volume_context_unlink(context->main_volume_context);
+ }
+ static void context_free(struct context *context) {
+@@ -177,132 +221,290 @@ static void context_free(struct context *context) {
+     if (!context->unlinked)
+         context_unlink(context);
+-    if (context->main_input_mute_control_target_info)
+-        pa_binding_target_info_free(context->main_input_mute_control_target_info);
+-
+-    if (context->main_output_mute_control_target_info)
+-        pa_binding_target_info_free(context->main_output_mute_control_target_info);
+-
+-    if (context->main_input_volume_control_target_info)
+-        pa_binding_target_info_free(context->main_input_volume_control_target_info);
+-
+-    if (context->main_output_volume_control_target_info)
+-        pa_binding_target_info_free(context->main_output_volume_control_target_info);
++    if (context->main_volume_context)
++        pa_main_volume_context_free(context->main_volume_context);
+-    pa_xfree(context->description);
+-    pa_xfree(context->name);
+     pa_xfree(context);
+ }
+-static void context_set_description(struct context *context, const char *description) {
+-    pa_assert(context);
+-    pa_assert(description);
+-
+-    pa_xfree(context->description);
+-    context->description = pa_xstrdup(description);
+-}
+-
+-static void context_set_main_control_target_info(struct context *context, enum control_type type, pa_direction_t direction,
+-                                                 pa_binding_target_info *info) {
++static struct control_info *context_get_control_info(struct context *context, enum control_type type,
++                                                     pa_direction_t direction) {
+     pa_assert(context);
+     switch (type) {
+         case CONTROL_TYPE_VOLUME:
+-            if (direction == PA_DIRECTION_OUTPUT) {
+-                if (context->main_output_volume_control_target_info)
+-                    pa_binding_target_info_free(context->main_output_volume_control_target_info);
+-
+-                if (info)
+-                    context->main_output_volume_control_target_info = pa_binding_target_info_copy(info);
+-                else
+-                    context->main_output_volume_control_target_info = NULL;
+-            } else {
+-                if (context->main_input_volume_control_target_info)
+-                    pa_binding_target_info_free(context->main_input_volume_control_target_info);
+-
+-                if (info)
+-                    context->main_input_volume_control_target_info = pa_binding_target_info_copy(info);
+-                else
+-                    context->main_input_volume_control_target_info = NULL;
++            switch (direction) {
++                case PA_DIRECTION_OUTPUT:
++                    return &context->output_volume_info;
++
++                case PA_DIRECTION_INPUT:
++                    return &context->input_volume_info;
+             }
+             break;
+         case CONTROL_TYPE_MUTE:
+-            if (direction == PA_DIRECTION_OUTPUT) {
+-                if (context->main_output_mute_control_target_info)
+-                    pa_binding_target_info_free(context->main_output_mute_control_target_info);
+-
+-                if (info)
+-                    context->main_output_mute_control_target_info = pa_binding_target_info_copy(info);
+-                else
+-                    context->main_output_mute_control_target_info = NULL;
+-            } else {
+-                if (context->main_input_mute_control_target_info)
+-                    pa_binding_target_info_free(context->main_input_mute_control_target_info);
+-
+-                if (info)
+-                    context->main_input_mute_control_target_info = pa_binding_target_info_copy(info);
+-                else
+-                    context->main_input_mute_control_target_info = NULL;
++            switch (direction) {
++                case PA_DIRECTION_OUTPUT:
++                    return &context->output_mute_info;
++
++                case PA_DIRECTION_INPUT:
++                    return &context->input_mute_info;
+             }
+             break;
+     }
++
++    pa_assert_not_reached();
++}
++
++static void context_set_binding_target(struct context *context, enum control_type type, pa_direction_t direction,
++                                       pa_audio_group *group) {
++    struct control_info *info;
++    void *control = NULL;
++
++    pa_assert(context);
++
++    info = context_get_control_info(context, type, direction);
++    info->binding_target = group;
++
++    if (group) {
++        switch (type) {
++            case CONTROL_TYPE_VOLUME:
++                control = group->volume_control;
++                break;
++
++            case CONTROL_TYPE_MUTE:
++                control = group->mute_control;
++                break;
++        }
++    }
++
++    info->set_control(context->main_volume_context, control);
++}
++
++static void context_set_binding_target_name(struct context *context, enum control_type type, pa_direction_t direction,
++                                            const char *name) {
++    struct control_info *info;
++    pa_audio_group *group = NULL;
++
++    pa_assert(context);
++
++    info = context_get_control_info(context, type, direction);
++
++    if (pa_safe_streq(name, info->binding_target_name))
++        return;
++
++    pa_xfree(info->binding_target_name);
++    info->binding_target_name = pa_xstrdup(name);
++
++    if (name)
++        group = pa_hashmap_get(context->userdata->volume_api->audio_groups, name);
++
++    context_set_binding_target(context, type, direction, group);
+ }
+ static pa_hook_result_t active_main_volume_context_changed_cb(void *hook_data, void *call_data, void *userdata) {
+     struct userdata *u = userdata;
+     pa_main_volume_context *context;
+-    pa_volume_api *api;
+-    pa_binding_target_info *info;
+     pa_assert(u);
+     context = u->main_volume_policy->active_main_volume_context;
+-    api = u->main_volume_policy->volume_api;
+     if (u->output_volume_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
+-        if (context) {
+-            info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
+-                                              PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL);
+-            pa_volume_api_bind_main_output_volume_control(api, info);
+-            pa_binding_target_info_free(info);
+-        } else
+-            pa_volume_api_set_main_output_volume_control(api, NULL);
++        if (context)
++            pa_volume_api_set_main_output_volume_control(u->volume_api, context->main_output_volume_control);
++        else
++            pa_volume_api_set_main_output_volume_control(u->volume_api, NULL);
+     }
+     if (u->input_volume_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
+-        if (context) {
+-            info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
+-                                              PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL);
+-            pa_volume_api_bind_main_input_volume_control(api, info);
+-            pa_binding_target_info_free(info);
+-        } else
+-            pa_volume_api_set_main_input_volume_control(api, NULL);
++        if (context)
++            pa_volume_api_set_main_input_volume_control(u->volume_api, context->main_input_volume_control);
++        else
++            pa_volume_api_set_main_input_volume_control(u->volume_api, NULL);
+     }
+     if (u->output_mute_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
+-        if (context) {
+-            info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
+-                                              PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL);
+-            pa_volume_api_bind_main_output_mute_control(api, info);
+-            pa_binding_target_info_free(info);
+-        } else
+-            pa_volume_api_set_main_output_mute_control(api, NULL);
++        if (context)
++            pa_volume_api_set_main_output_mute_control(u->volume_api, context->main_output_mute_control);
++        else
++            pa_volume_api_set_main_output_mute_control(u->volume_api, NULL);
+     }
+     if (u->input_mute_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
+-        if (context) {
+-            info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
+-                                              PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL);
+-            pa_volume_api_bind_main_input_mute_control(api, info);
+-            pa_binding_target_info_free(info);
+-        } else
+-            pa_volume_api_set_main_input_mute_control(api, NULL);
++        if (context)
++            pa_volume_api_set_main_input_mute_control(u->volume_api, context->main_input_mute_control);
++        else
++            pa_volume_api_set_main_input_mute_control(u->volume_api, NULL);
++    }
++
++    return PA_HOOK_OK;
++}
++
++static void handle_context_control_change(struct context *context, enum control_type type, pa_direction_t direction) {
++    struct control_info *info;
++
++    pa_assert(context);
++
++    info = context_get_control_info(context, type, direction);
++
++    if (*info->model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT
++            && context->userdata->main_volume_policy->active_main_volume_context == context->main_volume_context)
++        info->set_volume_api_control(context->userdata->volume_api, *info->control);
++}
++
++static pa_hook_result_t main_volume_context_main_output_volume_control_changed_cb(void *hook_data, void *call_data,
++                                                                                  void *userdata) {
++    pa_main_volume_context *context = call_data;
++
++    pa_assert(context);
++
++    handle_context_control_change(context->userdata, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t main_volume_context_main_input_volume_control_changed_cb(void *hook_data, void *call_data,
++                                                                                 void *userdata) {
++    pa_main_volume_context *context = call_data;
++
++    pa_assert(context);
++
++    handle_context_control_change(context->userdata, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t main_volume_context_main_output_mute_control_changed_cb(void *hook_data, void *call_data,
++                                                                                void *userdata) {
++    pa_main_volume_context *context = call_data;
++
++    pa_assert(context);
++
++    handle_context_control_change(context->userdata, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t main_volume_context_main_input_mute_control_changed_cb(void *hook_data, void *call_data,
++                                                                               void *userdata) {
++    pa_main_volume_context *context = call_data;
++
++    pa_assert(context);
++
++    handle_context_control_change(context->userdata, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t audio_group_put_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_audio_group *group = call_data;
++    struct context *context;
++    void *state;
++
++    pa_assert(u);
++    pa_assert(group);
++
++    PA_HASHMAP_FOREACH(context, u->contexts, state) {
++        if (context->output_volume_info.binding_target_name
++                && pa_streq(context->output_volume_info.binding_target_name, group->name))
++            context_set_binding_target(context, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT, group);
++
++        if (context->input_volume_info.binding_target_name
++                && pa_streq(context->input_volume_info.binding_target_name, group->name))
++            context_set_binding_target(context, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT, group);
++
++        if (context->output_mute_info.binding_target_name
++                && pa_streq(context->output_mute_info.binding_target_name, group->name))
++            context_set_binding_target(context, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT, group);
++
++        if (context->input_mute_info.binding_target_name
++                && pa_streq(context->input_mute_info.binding_target_name, group->name))
++            context_set_binding_target(context, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT, group);
++    }
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t audio_group_unlink_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_audio_group *group = call_data;
++    struct context *context;
++    void *state;
++
++    pa_assert(u);
++    pa_assert(group);
++
++    PA_HASHMAP_FOREACH(context, u->contexts, state) {
++        if (context->output_volume_info.binding_target == group)
++            context_set_binding_target(context, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT, NULL);
++
++        if (context->input_volume_info.binding_target == group)
++            context_set_binding_target(context, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT, NULL);
++
++        if (context->output_mute_info.binding_target == group)
++            context_set_binding_target(context, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT, NULL);
++
++        if (context->input_mute_info.binding_target == group)
++            context_set_binding_target(context, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT, NULL);
+     }
+     return PA_HOOK_OK;
+ }
++static void handle_audio_group_control_change(struct userdata *u, pa_audio_group *group, enum control_type type) {
++    struct context *context;
++    void *state;
++
++    pa_assert(u);
++    pa_assert(group);
++
++    PA_HASHMAP_FOREACH(context, u->contexts, state) {
++        switch (type) {
++            case CONTROL_TYPE_VOLUME:
++                if (context->output_volume_info.binding_target == group)
++                    pa_main_volume_context_set_main_output_volume_control(context->main_volume_context, group->volume_control);
++
++                if (context->input_volume_info.binding_target == group)
++                    pa_main_volume_context_set_main_input_volume_control(context->main_volume_context, group->volume_control);
++                break;
++
++            case CONTROL_TYPE_MUTE:
++                if (context->output_mute_info.binding_target == group)
++                    pa_main_volume_context_set_main_output_mute_control(context->main_volume_context, group->mute_control);
++
++                if (context->input_mute_info.binding_target == group)
++                    pa_main_volume_context_set_main_input_mute_control(context->main_volume_context, group->mute_control);
++                break;
++        }
++    }
++}
++
++static pa_hook_result_t audio_group_volume_control_changed_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_audio_group *group = call_data;
++
++    pa_assert(u);
++    pa_assert(group);
++
++    handle_audio_group_control_change(u, group, CONTROL_TYPE_VOLUME);
++
++    return PA_HOOK_OK;
++}
++
++static pa_hook_result_t audio_group_mute_control_changed_cb(void *hook_data, void *call_data, void *userdata) {
++    struct userdata *u = userdata;
++    pa_audio_group *group = call_data;
++
++    pa_assert(u);
++    pa_assert(group);
++
++    handle_audio_group_control_change(u, group, CONTROL_TYPE_MUTE);
++
++    return PA_HOOK_OK;
++}
++
+ static int parse_model(pa_config_parser_state *state) {
+     int r;
+@@ -315,105 +517,81 @@ static int parse_model(pa_config_parser_state *state) {
+     return r;
+ }
+-static int parse_main_volume_contexts(pa_config_parser_state *state) {
+-    struct userdata *u;
+-    char *name;
+-    const char *split_state = NULL;
+-
+-    pa_assert(state);
+-
+-    u = state->userdata;
+-
+-    while ((name = pa_split_spaces(state->rvalue, &split_state)))
+-        pa_hashmap_put(u->context_names, name, name);
+-
+-    return 0;
+-}
+-
+-static struct context *get_context(struct userdata *u, const char *section) {
++static int get_context(struct userdata *u, const char *section, struct context **_r) {
+     const char *name;
+     struct context *context;
+     pa_assert(u);
+     if (!section)
+-        return NULL;
++        return -PA_ERR_INVALID;
+     if (!pa_startswith(section, "MainVolumeContext "))
+-        return NULL;
++        return -PA_ERR_INVALID;
+     name = section + 18;
+-    context = pa_hashmap_get(u->unused_contexts, name);
++    context = pa_hashmap_get(u->contexts, name);
+     if (!context) {
+-        context = context_new(u, name);
+-        pa_hashmap_put(u->unused_contexts, context->name, context);
++        int r;
++
++        r = context_new(u, name, &context);
++        if (r < 0)
++            return r;
++
++        pa_hashmap_put(u->contexts, (void *) context->main_volume_context->name, context);
+     }
+-    return context;
++    *_r = context;
++    return 0;
+ }
+ static int parse_description(pa_config_parser_state *state) {
+     struct userdata *u;
++    int r;
+     struct context *context;
+     pa_assert(state);
+     u = state->userdata;
+-    context = get_context(u, state->section);
+-    if (!context) {
+-        pa_log("[%s:%u] Key \"%s\" not expected in section %s.", state->filename, state->lineno, state->lvalue,
++    r = get_context(u, state->section, &context);
++    if (r < 0) {
++        pa_log("[%s:%u] Couldn't get main volume context for section \"%s\".", state->filename, state->lineno,
+                pa_strnull(state->section));
+         return -PA_ERR_INVALID;
+     }
+-    context_set_description(context, state->rvalue);
++    pa_main_volume_context_set_description(context->main_volume_context, state->rvalue);
+     return 0;
+ }
+-static const char *get_target_field_name(enum control_type type) {
+-    switch (type) {
+-        case CONTROL_TYPE_VOLUME:
+-            return "volume_control";
+-
+-        case CONTROL_TYPE_MUTE:
+-            return "mute_control";
+-    }
+-
+-    pa_assert_not_reached();
+-}
+-
+-static int parse_main_control(pa_config_parser_state *state, enum control_type type, pa_direction_t direction) {
++static int parse_control(pa_config_parser_state *state, enum control_type type, pa_direction_t direction) {
+     struct userdata *u;
++    int r;
+     struct context *context;
+     pa_assert(state);
+     u = state->userdata;
+-    context = get_context(u, state->section);
+-    if (!context) {
+-        pa_log("[%s:%u] Key \"%s\" not expected in section %s.", state->filename, state->lineno, state->lvalue,
++    r = get_context(u, state->section, &context);
++    if (r < 0) {
++        pa_log("[%s:%u] Couldn't get main volume context for section \"%s\".", state->filename, state->lineno,
+                pa_strnull(state->section));
+         return -PA_ERR_INVALID;
+     }
+     if (pa_streq(state->rvalue, "none"))
+-        context_set_main_control_target_info(context, type, direction, NULL);
+-    else if (pa_startswith(state->rvalue, "bind:")) {
+-        int r;
+-        pa_binding_target_info *info;
+-
+-        r = pa_binding_target_info_new_from_string(state->rvalue, get_target_field_name(type), &info);
+-        if (r < 0) {
+-            pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue);
+-            return r;
++        context_set_binding_target_name(context, type, direction, NULL);
++    else if (pa_startswith(state->rvalue, BIND_PREFIX)) {
++        if (pa_startswith(state->rvalue, BIND_AUDIO_GROUP_PREFIX))
++            context_set_binding_target_name(context, type, direction, state->rvalue + strlen(BIND_AUDIO_GROUP_PREFIX));
++        else {
++            pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue + strlen(BIND_PREFIX));
++            return -PA_ERR_INVALID;
+         }
+-
+-        context_set_main_control_target_info(context, type, direction, info);
+-        pa_binding_target_info_free(info);
+     } else {
+         pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
+         return -PA_ERR_INVALID;
+@@ -425,69 +603,38 @@ static int parse_main_control(pa_config_parser_state *state, enum control_type t
+ static int parse_main_output_volume_control(pa_config_parser_state *state) {
+     pa_assert(state);
+-    return parse_main_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT);
++    return parse_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT);
+ }
+ static int parse_main_input_volume_control(pa_config_parser_state *state) {
+     pa_assert(state);
+-    return parse_main_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT);
++    return parse_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT);
+ }
+ static int parse_main_output_mute_control(pa_config_parser_state *state) {
+     pa_assert(state);
+-    return parse_main_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT);
++    return parse_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT);
+ }
+ static int parse_main_input_mute_control(pa_config_parser_state *state) {
+     pa_assert(state);
+-    return parse_main_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT);
+-}
+-
+-static void finalize_config(struct userdata *u) {
+-    const char *context_name;
+-    void *state;
+-    struct context *context;
+-
+-    pa_assert(u);
+-
+-    PA_HASHMAP_FOREACH(context_name, u->context_names, state) {
+-        int r;
+-
+-        context = pa_hashmap_remove(u->unused_contexts, context_name);
+-        if (!context)
+-            context = context_new(u, context_name);
+-
+-        r = context_put(context);
+-        if (r < 0) {
+-            pa_log_warn("Failed to create main volume context %s.", context_name);
+-            context_free(context);
+-            continue;
+-        }
+-
+-        pa_assert_se(pa_hashmap_put(u->contexts, context->name, context) >= 0);
+-    }
+-
+-    PA_HASHMAP_FOREACH(context, u->unused_contexts, state)
+-        pa_log_debug("Main volume context %s is not used.", context->name);
+-
+-    pa_hashmap_free(u->unused_contexts);
+-    u->unused_contexts = NULL;
+-
+-    pa_hashmap_free(u->context_names);
+-    u->context_names = NULL;
++    return parse_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT);
+ }
+ int pa__init(pa_module *module) {
+     struct userdata *u;
+     FILE *f;
+     char *fn = NULL;
++    struct context *context;
++    void *state;
+     pa_assert(module);
+     u = module->userdata = pa_xnew0(struct userdata, 1);
++    u->volume_api = pa_volume_api_get(module->core);
+     u->main_volume_policy = pa_main_volume_policy_get(module->core);
+     u->output_volume_model = MODEL_NONE;
+     u->input_volume_model = MODEL_NONE;
+@@ -498,9 +645,32 @@ int pa__init(pa_module *module) {
+     u->active_main_volume_context_changed_slot =
+             pa_hook_connect(&u->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_ACTIVE_MAIN_VOLUME_CONTEXT_CHANGED],
+                             PA_HOOK_NORMAL, active_main_volume_context_changed_cb, u);
+-    u->context_names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
+-    u->unused_contexts = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
+-                                             (pa_free_cb_t) context_free);
++    u->main_volume_context_main_output_volume_control_changed_slot =
++            pa_hook_connect(&u->main_volume_policy->hooks
++                                [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED],
++                            PA_HOOK_NORMAL, main_volume_context_main_output_volume_control_changed_cb, u);
++    u->main_volume_context_main_input_volume_control_changed_slot =
++            pa_hook_connect(&u->main_volume_policy->hooks
++                                [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_VOLUME_CONTROL_CHANGED],
++                            PA_HOOK_NORMAL, main_volume_context_main_input_volume_control_changed_cb, u);
++    u->main_volume_context_main_output_mute_control_changed_slot =
++            pa_hook_connect(&u->main_volume_policy->hooks
++                                [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_MUTE_CONTROL_CHANGED],
++                            PA_HOOK_NORMAL, main_volume_context_main_output_mute_control_changed_cb, u);
++    u->main_volume_context_main_input_mute_control_changed_slot =
++            pa_hook_connect(&u->main_volume_policy->hooks
++                                [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_MUTE_CONTROL_CHANGED],
++                            PA_HOOK_NORMAL, main_volume_context_main_input_mute_control_changed_cb, u);
++    u->audio_group_put_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_PUT], PA_HOOK_NORMAL,
++                                              audio_group_put_cb, u);
++    u->audio_group_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK], PA_HOOK_NORMAL,
++                                                 audio_group_unlink_cb, u);
++    u->audio_group_volume_control_changed_slot =
++            pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_VOLUME_CONTROL_CHANGED], PA_HOOK_NORMAL,
++                            audio_group_volume_control_changed_cb, u);
++    u->audio_group_mute_control_changed_slot =
++            pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_MUTE_CONTROL_CHANGED], PA_HOOK_NORMAL,
++                            audio_group_mute_control_changed_cb, u);
+     f = pa_open_config_file(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "main-volume-policy.conf", "main-volume-policy.conf", NULL, &fn);
+     if (f) {
+@@ -509,7 +679,6 @@ int pa__init(pa_module *module) {
+             { "input-volume-model", parse_model, &u->input_volume_model, "General" },
+             { "output-mute-model", parse_model, &u->output_mute_model, "General" },
+             { "input-mute-model", parse_model, &u->input_mute_model, "General" },
+-            { "main-volume-contexts", parse_main_volume_contexts, NULL, "General" },
+             { "description", parse_description, NULL, NULL },
+             { "main-output-volume-control", parse_main_output_volume_control, NULL, NULL },
+             { "main-input-volume-control", parse_main_input_volume_control, NULL, NULL },
+@@ -525,7 +694,8 @@ int pa__init(pa_module *module) {
+         f = NULL;
+     }
+-    finalize_config(u);
++    PA_HASHMAP_FOREACH(context, u->contexts, state)
++        context_put(context);
+     pa_log_debug("Output volume model: %s", model_to_string(u->output_volume_model));
+     pa_log_debug("Input volume model: %s", model_to_string(u->input_volume_model));
+@@ -544,6 +714,30 @@ void pa__done(pa_module *module) {
+     if (!u)
+         return;
++    if (u->audio_group_mute_control_changed_slot)
++        pa_hook_slot_free(u->audio_group_mute_control_changed_slot);
++
++    if (u->audio_group_volume_control_changed_slot)
++        pa_hook_slot_free(u->audio_group_volume_control_changed_slot);
++
++    if (u->audio_group_unlink_slot)
++        pa_hook_slot_free(u->audio_group_unlink_slot);
++
++    if (u->audio_group_put_slot)
++        pa_hook_slot_free(u->audio_group_put_slot);
++
++    if (u->main_volume_context_main_input_mute_control_changed_slot)
++        pa_hook_slot_free(u->main_volume_context_main_input_mute_control_changed_slot);
++
++    if (u->main_volume_context_main_output_mute_control_changed_slot)
++        pa_hook_slot_free(u->main_volume_context_main_output_mute_control_changed_slot);
++
++    if (u->main_volume_context_main_input_volume_control_changed_slot)
++        pa_hook_slot_free(u->main_volume_context_main_input_volume_control_changed_slot);
++
++    if (u->main_volume_context_main_output_volume_control_changed_slot)
++        pa_hook_slot_free(u->main_volume_context_main_output_volume_control_changed_slot);
++
+     if (u->active_main_volume_context_changed_slot)
+         pa_hook_slot_free(u->active_main_volume_context_changed_slot);
+@@ -553,5 +747,8 @@ void pa__done(pa_module *module) {
+     if (u->main_volume_policy)
+         pa_main_volume_policy_unref(u->main_volume_policy);
++    if (u->volume_api)
++        pa_volume_api_unref(u->volume_api);
++
+     pa_xfree(u);
+ }
+diff --git a/src/modules/volume-api/audio-group.c b/src/modules/volume-api/audio-group.c
+index 76bfa69..66e0f8a 100644
+--- a/src/modules/volume-api/audio-group.c
++++ b/src/modules/volume-api/audio-group.c
+@@ -29,34 +29,33 @@
+ #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 pa_audio_group_new(pa_volume_api *api, const char *name, pa_audio_group **_r) {
++    pa_audio_group *group = NULL;
+     int r;
+     pa_assert(api);
+     pa_assert(name);
+-    pa_assert(description);
+-    pa_assert(group);
++    pa_assert(_r);
+-    group_local = pa_xnew0(pa_audio_group, 1);
+-    group_local->volume_api = api;
+-    group_local->index = pa_volume_api_allocate_audio_group_index(api);
++    group = pa_xnew0(pa_audio_group, 1);
++    group->volume_api = api;
++    group->index = pa_volume_api_allocate_audio_group_index(api);
+-    r = pa_volume_api_register_name(api, name, true, &group_local->name);
++    r = pa_volume_api_register_name(api, name, true, &group->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;
++    group->description = pa_xstrdup(group->name);
++    group->proplist = pa_proplist_new();
++    group->volume_streams = pa_hashmap_new(NULL, NULL);
++    group->mute_streams = pa_hashmap_new(NULL, NULL);
++    *_r = group;
+     return 0;
+ fail:
+-    pa_audio_group_free(group_local);
++    if (group)
++        pa_audio_group_free(group);
+     return r;
+ }
+@@ -68,7 +67,6 @@ void pa_audio_group_put(pa_audio_group *group) {
+     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);
+@@ -99,9 +97,9 @@ void pa_audio_group_unlink(pa_audio_group *group) {
+     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);
+-    pa_volume_api_remove_audio_group(group->volume_api, group);
++    pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK], group);
+     while ((stream = pa_hashmap_first(group->mute_streams)))
+         pas_stream_set_audio_group_for_mute(stream, NULL);
+@@ -109,34 +107,15 @@ void pa_audio_group_unlink(pa_audio_group *group) {
+     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;
+-    }
++    pa_audio_group_set_mute_control(group, NULL);
++    pa_audio_group_set_volume_control(group, NULL);
+ }
+ void pa_audio_group_free(pa_audio_group *group) {
+     pa_assert(group);
+-    if (!group->unlinked)
++    /* unlink() expects name to be set. */
++    if (!group->unlinked && group->name)
+         pa_audio_group_unlink(group);
+     if (group->mute_streams)
+@@ -156,133 +135,33 @@ void pa_audio_group_free(pa_audio_group *group) {
+     pa_xfree(group);
+ }
+-const char *pa_audio_group_get_name(pa_audio_group *group) {
+-    pa_assert(group);
++void pa_audio_group_set_description(pa_audio_group *group, const char *description) {
++    char *old_description;
+-    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);
++    pa_assert(description);
+-    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;
++    old_description = group->description;
+-    pa_assert(control);
++    if (pa_streq(description, old_description))
++        return;
+-    group = control->userdata;
++    group->description = pa_xstrdup(description);
+-    PA_HASHMAP_FOREACH(stream, group->mute_streams, state) {
+-        if (stream->own_mute_control)
+-            pa_mute_control_set_mute(stream->own_mute_control, control->mute);
++    if (!group->linked || group->unlinked) {
++        pa_xfree(old_description);
++        return;
+     }
+-}
+-void pa_audio_group_set_have_own_mute_control(pa_audio_group *group, bool have) {
+-    pa_assert(group);
++    pa_log_debug("The description of audio group %s changed from \"%s\" to \"%s\".", group->name, old_description,
++                 description);
++    pa_xfree(old_description);
+-    if (have == group->have_own_mute_control)
+-        return;
++    pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_DESCRIPTION_CHANGED], group);
+-    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) {
++void pa_audio_group_set_volume_control(pa_audio_group *group, pa_volume_control *control) {
+     pa_volume_control *old_control;
+     pa_assert(group);
+@@ -292,14 +171,8 @@ static void set_volume_control_internal(pa_audio_group *group, pa_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;
+@@ -309,18 +182,7 @@ static void set_volume_control_internal(pa_audio_group *group, pa_volume_control
+     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) {
++void pa_audio_group_set_mute_control(pa_audio_group *group, pa_mute_control *control) {
+     pa_mute_control *old_control;
+     pa_assert(group);
+@@ -330,14 +192,8 @@ static void set_mute_control_internal(pa_audio_group *group, pa_mute_control *co
+     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;
+@@ -347,57 +203,11 @@ static void set_mute_control_internal(pa_audio_group *group, pa_mute_control *co
+     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) {
+@@ -405,8 +215,6 @@ void pa_audio_group_remove_volume_stream(pa_audio_group *group, pas_stream *stre
+     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) {
+@@ -414,11 +222,6 @@ void pa_audio_group_add_mute_stream(pa_audio_group *group, pas_stream *stream) {
+     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) {
+@@ -426,23 +229,4 @@ void pa_audio_group_remove_mute_stream(pa_audio_group *group, pas_stream *stream
+     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
+index 41591ba..02db3eb 100644
+--- a/src/modules/volume-api/audio-group.h
++++ b/src/modules/volume-api/audio-group.h
+@@ -22,7 +22,6 @@
+   USA.
+ ***/
+-#include <modules/volume-api/binding.h>
+ #include <modules/volume-api/mute-control.h>
+ #include <modules/volume-api/volume-control.h>
+@@ -32,10 +31,6 @@
+ 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;
+@@ -44,13 +39,7 @@ struct pa_audio_group {
+     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) */
+@@ -58,28 +47,22 @@ struct pa_audio_group {
+     bool unlinked;
+ };
+-int pa_audio_group_new(pa_volume_api *api, const char *name, const char *description, pa_audio_group **group);
++int pa_audio_group_new(pa_volume_api *api, const char *name, pa_audio_group **_r);
+ 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);
++/* Called by the audio group implementation. */
++void pa_audio_group_set_description(pa_audio_group *group, const char *description);
+ 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. */
++/* Called by sstream.c only. If you want to assign a stream to an audio group, use
++ * pas_stream_set_audio_group_for_volume() and
++ * pas_stream_set_audio_group_for_mute(). */
+ 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
+deleted file mode 100644
+index 6e73119..0000000
+--- a/src/modules/volume-api/binding.c
++++ /dev/null
+@@ -1,386 +0,0 @@
+-/***
+-  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
+deleted file mode 100644
+index ba4dea8..0000000
+--- a/src/modules/volume-api/binding.h
++++ /dev/null
+@@ -1,128 +0,0 @@
+-#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
+index 0317fb6..75545dd 100644
+--- a/src/modules/volume-api/bvolume.h
++++ b/src/modules/volume-api/bvolume.h
+@@ -29,13 +29,16 @@ 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 pa_ext_volume_api_bvolume_init
+ #define pa_bvolume_init_mono pa_ext_volume_api_bvolume_init_mono
++#define pa_bvolume_parse_balance pa_ext_volume_api_bvolume_parse_balance
+ #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_balance_to_string pa_ext_volume_api_bvolume_balance_to_string
+ #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
+diff --git a/src/modules/volume-api/device-creator.c b/src/modules/volume-api/device-creator.c
+index f35fab0..fc486f8 100644
+--- a/src/modules/volume-api/device-creator.c
++++ b/src/modules/volume-api/device-creator.c
+@@ -59,6 +59,8 @@ struct device_volume_control {
+     pa_hook_slot *volume_changed_slot;
+ };
++static void device_volume_control_free(struct device_volume_control *control);
++
+ struct device_mute_control {
+     struct device *device;
+     pa_mute_control *mute_control;
+@@ -68,6 +70,8 @@ struct device_mute_control {
+     pa_hook_slot *mute_changed_slot;
+ };
++static void device_mute_control_free(struct device_mute_control *control);
++
+ struct device {
+     pa_device_creator *creator;
+     enum device_type type;
+@@ -85,6 +89,8 @@ struct device {
+     struct device *monitor;
+ };
++static void device_free(struct device *device);
++
+ static const char *device_type_from_icon_name(const char *icon_name) {
+     if (!icon_name)
+         return NULL;
+@@ -168,112 +174,6 @@ static const char *get_source_description(pa_source *source) {
+     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;
+@@ -309,24 +209,55 @@ static pa_hook_result_t sink_or_source_volume_changed_cb(void *hook_data, void *
+     if (sink)
+         pa_bvolume_from_cvolume(&bvolume, &sink->reference_volume, &sink->channel_map);
+-    else
++    else if (source)
+         pa_bvolume_from_cvolume(&bvolume, &source->reference_volume, &source->channel_map);
++    else
++        pa_assert_not_reached();
+-    pa_volume_control_volume_changed(control->volume_control, &bvolume, true, true);
++    pa_volume_control_set_volume(control->volume_control, &bvolume, true, true);
+     return PA_HOOK_OK;
+ }
+-static void volume_control_set_initial_volume_cb(pa_volume_control *c) {
++static int volume_control_set_volume_cb(pa_volume_control *c, const pa_bvolume *original_volume,
++                                        const pa_bvolume *remapped_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(original_volume);
++    pa_assert(remapped_volume);
+     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_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 = remapped_volume->volume;
++
++    if (set_balance)
++        pa_bvolume_copy_balance(&bvolume, remapped_volume);
++
++    pa_bvolume_to_cvolume(&bvolume, &cvolume);
+     switch (device->type) {
+         case DEVICE_TYPE_PORT:
+@@ -345,44 +276,91 @@ static void volume_control_set_initial_volume_cb(pa_volume_control *c) {
+             pa_sink_set_volume(device->sink, &cvolume, true, true);
+             break;
+     }
++
++    return 0;
+ }
+-static void device_volume_control_put(struct device_volume_control *control) {
+-    struct device *device;
++static int device_volume_control_new(struct device *device, struct device_volume_control **_r) {
++    struct device_volume_control *control = NULL;
++    const char *name = NULL;
+     pa_bvolume volume;
++    bool convertible_to_dB = false;
++    int r;
+-    pa_assert(control);
++    pa_assert(device);
++    pa_assert(_r);
+-    device = control->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) {
+                 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);
++                convertible_to_dB = device->sink->flags & PA_SINK_DECIBEL_VOLUME;
+             } 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);
++                convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME;
+             }
++
+             break;
+         case DEVICE_TYPE_PORT_MONITOR:
+-        case DEVICE_TYPE_SOURCE:
++            name = "port-monitor-volume-control";
+             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);
++            convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME;
+             break;
+         case DEVICE_TYPE_SINK:
++            name = "sink-volume-control";
+             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);
++            convertible_to_dB = device->sink->flags & PA_SINK_DECIBEL_VOLUME;
++            break;
++
++        case DEVICE_TYPE_SOURCE:
++            name = "source-volume-control";
++            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);
++            convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME;
+             break;
+     }
+-    pa_volume_control_put(control->volume_control, &volume, volume_control_set_initial_volume_cb);
++    r = pa_volume_control_new(device->creator->volume_api, name, false, &control->volume_control);
++    if (r < 0)
++        goto fail;
++
++    pa_volume_control_set_description(control->volume_control, device->device->description);
++    pa_volume_control_set_channel_map(control->volume_control, &volume.channel_map);
++    pa_volume_control_set_volume(control->volume_control, &volume, true, true);
++    pa_volume_control_set_convertible_to_dB(control->volume_control, convertible_to_dB);
++    control->volume_control->set_volume = volume_control_set_volume_cb;
++    control->volume_control->userdata = control;
++
++    *_r = control;
++    return 0;
++
++fail:
++    if (control)
++        device_volume_control_free(control);
++
++    return r;
++}
++
++static void device_volume_control_put(struct device_volume_control *control) {
++    pa_assert(control);
++
++    pa_volume_control_put(control->volume_control);
+ }
+ static void device_volume_control_unlink(struct device_volume_control *control) {
+@@ -395,11 +373,6 @@ static void device_volume_control_unlink(struct device_volume_control *control)
+     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) {
+@@ -411,71 +384,10 @@ static void device_volume_control_free(struct device_volume_control *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;
++    if (control->volume_changed_slot)
++        pa_hook_slot_free(control->volume_changed_slot);
+-    return control;
++    pa_xfree(control);
+ }
+ static pa_hook_result_t sink_or_source_mute_changed_cb(void *hook_data, void *call_data, void *userdata) {
+@@ -518,13 +430,13 @@ static pa_hook_result_t sink_or_source_mute_changed_cb(void *hook_data, void *ca
+     else
+         pa_assert_not_reached();
+-    pa_mute_control_mute_changed(control->mute_control, mute);
++    pa_mute_control_set_mute(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;
++static int mute_control_set_mute_cb(pa_mute_control *c, bool mute) {
++    struct device_mute_control *control;
+     struct device *device;
+     pa_assert(c);
+@@ -535,32 +447,40 @@ static void mute_control_set_initial_mute_cb(pa_mute_control *c) {
+     switch (device->type) {
+         case DEVICE_TYPE_PORT:
+             if (device->port->direction == PA_DIRECTION_OUTPUT)
+-                pa_sink_set_mute(device->sink, c->mute, true);
++                pa_sink_set_mute(device->sink, mute, true);
+             else
+-                pa_source_set_mute(device->source, c->mute, true);
++                pa_source_set_mute(device->source, mute, true);
+             break;
+         case DEVICE_TYPE_PORT_MONITOR:
+         case DEVICE_TYPE_SOURCE:
+-            pa_source_set_mute(device->source, c->mute, true);
++            pa_source_set_mute(device->source, mute, true);
+             break;
+         case DEVICE_TYPE_SINK:
+-            pa_sink_set_mute(device->sink, c->mute, true);
++            pa_sink_set_mute(device->sink, mute, true);
+             break;
+     }
++
++    return 0;
+ }
+-static void device_mute_control_put(struct device_mute_control *control) {
+-    struct device *device;
++static int device_mute_control_new(struct device *device, struct device_mute_control **_r) {
++    struct device_mute_control *control = NULL;
++    const char *name = NULL;
+     bool mute = false;
++    int r;
+-    pa_assert(control);
++    pa_assert(device);
++    pa_assert(_r);
+-    device = control->device;
++    control = pa_xnew0(struct device_mute_control, 1);
++    control->device = device;
+     switch (device->type) {
+         case DEVICE_TYPE_PORT:
++            name = "port-mute-control";
++
+             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);
+@@ -573,20 +493,50 @@ static void device_mute_control_put(struct device_mute_control *control) {
+             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],
++            name = "port-monitor-mute-control";
++            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_SINK:
++            name = "sink-mute-control";
+             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;
++
++        case DEVICE_TYPE_SOURCE:
++            name = "source-mute-control";
++            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;
+     }
+-    pa_mute_control_put(control->mute_control, mute, true, mute_control_set_initial_mute_cb);
++    r = pa_mute_control_new(device->creator->volume_api, name, false, &control->mute_control);
++    if (r < 0)
++        goto fail;
++
++    pa_mute_control_set_description(control->mute_control, device->device->description);
++    pa_mute_control_set_mute(control->mute_control, mute);
++    control->mute_control->set_mute = mute_control_set_mute_cb;
++    control->mute_control->userdata = control;
++
++    *_r = control;
++    return 0;
++
++fail:
++    if (control)
++        device_mute_control_free(control);
++
++    return r;
++}
++
++static void device_mute_control_put(struct device_mute_control *control) {
++    pa_assert(control);
++
++    pa_mute_control_put(control->mute_control);
+ }
+ static void device_mute_control_unlink(struct device_mute_control *control) {
+@@ -599,11 +549,6 @@ static void device_mute_control_unlink(struct device_mute_control *control) {
+     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) {
+@@ -615,6 +560,9 @@ static void device_mute_control_free(struct device_mute_control *control) {
+     if (control->mute_control)
+         pa_mute_control_free(control->mute_control);
++    if (control->mute_changed_slot)
++        pa_hook_slot_free(control->mute_changed_slot);
++
+     pa_xfree(control);
+ }
+@@ -707,22 +655,24 @@ static pa_hook_result_t sink_or_source_proplist_changed_cb(void *hook_data, void
+     }
+     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);
++    pa_volume_control_set_description(device->volume_control->volume_control, description);
++    pa_mute_control_set_description(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) {
++static int device_new(pa_device_creator *creator, enum device_type type, void *core_device, struct device **_r) {
+     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;
++    int r;
+     pa_assert(creator);
+     pa_assert(core_device);
++    pa_assert(_r);
+     device = pa_xnew0(struct device, 1);
+     device->creator = creator;
+@@ -767,18 +717,20 @@ static struct device *device_new(pa_device_creator *creator, enum device_type ty
+             break;
+     }
+-    device->device = pa_device_new(creator->volume_api, name, description, direction, &device_type, device_type ? 1 : 0);
++    r = pa_device_new(creator->volume_api, name, description, direction, &device_type, device_type ? 1 : 0, &device->device);
+     pa_xfree(description);
++    if (r < 0)
++        goto fail;
+     if (create_volume_and_mute_controls) {
+-        device->volume_control = device_volume_control_new(device);
+-        device->mute_control = device_mute_control_new(device);
++        device_volume_control_new(device, &device->volume_control);
++        device_mute_control_new(device, &device->mute_control);
+     }
+     switch (type) {
+         case DEVICE_TYPE_PORT:
+             if (device->port->direction == PA_DIRECTION_OUTPUT)
+-                device->monitor = device_new(creator, DEVICE_TYPE_PORT_MONITOR, device->port);
++                device_new(creator, DEVICE_TYPE_PORT_MONITOR, device->port, &device->monitor);
+             break;
+         case DEVICE_TYPE_PORT_MONITOR:
+@@ -795,7 +747,14 @@ static struct device *device_new(pa_device_creator *creator, enum device_type ty
+             break;
+     }
+-    return device;
++    *_r = device;
++    return 0;
++
++fail:
++    if (device)
++        device_free(device);
++
++    return r;
+ }
+ static pa_hook_result_t port_active_changed_cb(void *hook_data, void *call_data, void *userdata) {
+@@ -825,25 +784,36 @@ static pa_hook_result_t port_active_changed_cb(void *hook_data, void *call_data,
+             pa_assert_not_reached();
+     }
+-    if (should_have_volume_and_mute_controls && !device->volume_control) {
+-        pa_assert(!device->mute_control);
++    if (should_have_volume_and_mute_controls) {
++        int r;
+-        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);
++        if (!device->volume_control) {
++            r = device_volume_control_new(device, &device->volume_control);
++            if (r >= 0) {
++                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 (!device->mute_control) {
++            r = device_mute_control_new(device, &device->mute_control);
++            if (r >= 0) {
++                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);
++    if (!should_have_volume_and_mute_controls) {
++        if (device->mute_control) {
++            device_mute_control_free(device->mute_control);
++            device->mute_control = NULL;
++        }
+-        device_mute_control_free(device->mute_control);
+-        device->mute_control = NULL;
+-        device_volume_control_free(device->volume_control);
+-        device->volume_control = NULL;
++        if (device->volume_control) {
++            device_volume_control_free(device->volume_control);
++            device->volume_control = NULL;
++        }
+     }
+     return PA_HOOK_OK;
+@@ -928,6 +898,7 @@ static void device_free(struct device *device) {
+ static void create_device(pa_device_creator *creator, enum device_type type, void *core_device) {
+     struct device *device;
++    int r;
+     pa_assert(creator);
+     pa_assert(core_device);
+@@ -956,9 +927,11 @@ static void create_device(pa_device_creator *creator, enum device_type type, voi
+         }
+     }
+-    device = device_new(creator, type, core_device);
+-    pa_hashmap_put(creator->devices, core_device, device);
+-    device_put(device);
++    r = device_new(creator, type, core_device, &device);
++    if (r >= 0) {
++        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) {
+diff --git a/src/modules/volume-api/device.c b/src/modules/volume-api/device.c
+index ea496ba..c1a580c 100644
+--- a/src/modules/volume-api/device.c
++++ b/src/modules/volume-api/device.c
+@@ -32,20 +32,26 @@
+ #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;
++int 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 **_r) {
++    pa_device *device = NULL;
++    int r;
+     unsigned i;
+     pa_assert(api);
+     pa_assert(name);
+     pa_assert(description);
+     pa_assert(device_types || n_device_types == 0);
++    pa_assert(_r);
+     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);
++
++    r = pa_volume_api_register_name(api, name, false, &device->name);
++    if (r < 0)
++        goto fail;
++
+     device->description = pa_xstrdup(description);
+     device->direction = direction;
+     device->device_types = pa_dynarray_new(pa_xfree);
+@@ -57,7 +63,14 @@ pa_device *pa_device_new(pa_volume_api *api, const char *name, const char *descr
+     device->use_default_volume_control = true;
+     device->use_default_mute_control = true;
+-    return device;
++    *_r = device;
++    return 0;
++
++fail:
++    if (device)
++        pa_device_free(device);
++
++    return r;
+ }
+ void pa_device_put(pa_device *device, pa_volume_control *default_volume_control, pa_mute_control *default_mute_control) {
+@@ -84,7 +97,6 @@ void pa_device_put(pa_device *device, pa_volume_control *default_volume_control,
+     }
+     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),
+@@ -120,35 +132,21 @@ void pa_device_unlink(pa_device *device) {
+     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);
++        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;
+-    }
++    pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_UNLINK], device);
+-    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;
+-    }
++    pa_device_set_mute_control(device, NULL);
++    pa_device_set_default_mute_control(device, NULL);
++    pa_device_set_volume_control(device, NULL);
++    pa_device_set_default_volume_control(device, NULL);
+ }
+ void pa_device_free(pa_device *device) {
+     pa_assert(device);
+-    if (!device->unlinked)
++    /* unlink() expects name to be set. */
++    if (!device->unlinked && device->name)
+         pa_device_unlink(device);
+     if (device->proplist)
+diff --git a/src/modules/volume-api/device.h b/src/modules/volume-api/device.h
+index 9eac7e9..8bd5158 100644
+--- a/src/modules/volume-api/device.h
++++ b/src/modules/volume-api/device.h
+@@ -51,8 +51,8 @@ struct pa_device {
+     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);
++int 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 **_r);
+ 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);
+diff --git a/src/modules/volume-api/inidb.c b/src/modules/volume-api/inidb.c
+new file mode 100644
+index 0000000..8116e72
+--- /dev/null
++++ b/src/modules/volume-api/inidb.c
+@@ -0,0 +1,553 @@
++/***
++  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 "inidb.h"
++
++#include <pulse/mainloop-api.h>
++#include <pulse/rtclock.h>
++#include <pulse/timeval.h>
++#include <pulse/xmalloc.h>
++
++#include <pulsecore/conf-parser.h>
++#include <pulsecore/core-error.h>
++#include <pulsecore/core-rtclock.h>
++#include <pulsecore/core-util.h>
++#include <pulsecore/hashmap.h>
++#include <pulsecore/macro.h>
++#include <pulsecore/namereg.h>
++
++#include <errno.h>
++
++#define SAVE_INTERVAL_USEC (10 * PA_USEC_PER_SEC)
++
++struct pa_inidb {
++    pa_core *core;
++    char *name;
++    char *file_path;
++    char *tmp_file_path;
++    pa_hashmap *tables; /* table name -> pa_inidb_table */
++    pa_time_event *time_event;
++    bool failed;
++    void *userdata;
++};
++
++struct pa_inidb_table {
++    pa_inidb *db;
++    char *name;
++    pa_hashmap *columns; /* column name -> column */
++    pa_hashmap *rows; /* row id -> pa_inidb_row */
++    pa_inidb_get_object_cb_t get_object;
++};
++
++struct column {
++    char *name;
++    pa_inidb_parse_cb_t parse;
++};
++
++struct pa_inidb_row {
++    char *id;
++    char *header;
++    pa_hashmap *cells; /* column name -> cell */
++};
++
++struct pa_inidb_cell {
++    pa_inidb *db;
++    struct column *column;
++    char *value;
++    char *assignment;
++};
++
++static void save(pa_inidb *db);
++
++static pa_inidb_table *table_new(pa_inidb *db, const char *name, pa_inidb_get_object_cb_t get_object_cb);
++static void table_free(pa_inidb_table *table);
++static pa_inidb_row *table_add_row_internal(pa_inidb_table *table, const char *row_id);
++
++static struct column *column_new(const char *name, pa_inidb_parse_cb_t parse_cb);
++static void column_free(struct column *column);
++
++static pa_inidb_row *row_new(pa_inidb_table *table, const char *id);
++static void row_free(pa_inidb_row *row);
++
++static pa_inidb_cell *cell_new(pa_inidb *db, struct column *column);
++static void cell_free(pa_inidb_cell *cell);
++static void cell_set_value_internal(pa_inidb_cell *cell, const char *value);
++
++static int parse_assignment(pa_config_parser_state *state) {
++    pa_inidb *db;
++    const char *end_of_table_name;
++    size_t table_name_len;
++    char *table_name;
++    pa_inidb_table *table;
++    const char *row_id;
++    pa_inidb_row *row;
++    int r;
++    void *object;
++    struct column *column;
++    pa_inidb_cell *cell;
++
++    pa_assert(state);
++
++    db = state->userdata;
++
++    /* FIXME: pa_config_parser should be improved so that it could parse the
++     * table name and row id for us in the section header. */
++    end_of_table_name = strchr(state->section, ' ');
++    if (!end_of_table_name) {
++        pa_log("[%s:%u] Failed to parse table name and row id in section \"%s\"", state->filename, state->lineno,
++               state->section);
++        return -PA_ERR_INVALID;
++    }
++
++    table_name_len = end_of_table_name - state->section;
++    table_name = pa_xstrndup(state->section, table_name_len);
++    table = pa_hashmap_get(db->tables, table_name);
++    if (!table)
++        pa_log("[%s:%u] Unknown table name: \"%s\"", state->filename, state->lineno, table_name);
++    pa_xfree(table_name);
++    if (!table)
++        return -PA_ERR_INVALID;
++
++    row_id = end_of_table_name + 1;
++    if (!pa_namereg_is_valid_name(row_id)) {
++        pa_log("[%s:%u] Invalid row id: \"%s\"", state->filename, state->lineno, row_id);
++        return -PA_ERR_INVALID;
++    }
++
++    /* This is not strictly necessary, but we do this to avoid saving the
++     * database when there is no actual change. Without this, the get_object()
++     * callback would cause redundant saving whenever creating new objects. */
++    if (!(row = pa_hashmap_get(table->rows, row_id)))
++        row = table_add_row_internal(table, row_id);
++
++    r = table->get_object(db, row_id, &object);
++    if (r < 0) {
++        pa_log("[%s:%u] Failed to create object %s.", state->filename, state->lineno, row_id);
++        return r;
++    }
++
++    column = pa_hashmap_get(table->columns, state->lvalue);
++    if (!column) {
++        pa_log("[%s:%u] Unknown column name: \"%s\"", state->filename, state->lineno, state->lvalue);
++        return -PA_ERR_INVALID;
++    }
++
++    /* This is not strictly necessary, but we do this to avoid saving the
++     * database when there is no actual change. Without this, the parse()
++     * callback would cause redundant saving whenever setting the cell value
++     * for the first time. */
++    cell = pa_hashmap_get(row->cells, column->name);
++    cell_set_value_internal(cell, state->rvalue);
++
++    r = column->parse(db, state->rvalue, object);
++    if (r < 0) {
++        pa_log("[%s:%u] Failed to parse %s value \"%s\".", state->filename, state->lineno, column->name, state->rvalue);
++        return r;
++    }
++
++    return 0;
++}
++
++pa_inidb *pa_inidb_new(pa_core *core, const char *name, void *userdata) {
++    pa_inidb *db;
++    int r;
++
++    pa_assert(core);
++    pa_assert(name);
++
++    db = pa_xnew0(pa_inidb, 1);
++    db->core = core;
++    db->name = pa_xstrdup(name);
++
++    r = pa_append_to_config_home_dir(name, true, &db->file_path);
++    if (r < 0) {
++        pa_log("Failed to find the file location for database \"%s\". The database will start empty, and updates will not be "
++               "saved on disk.", name);
++        db->failed = true;
++    }
++
++    if (db->file_path)
++        db->tmp_file_path = pa_sprintf_malloc("%s.tmp", db->file_path);
++
++    db->tables = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
++                                     (pa_free_cb_t) table_free);
++    db->userdata = userdata;
++
++    return db;
++}
++
++void pa_inidb_free(pa_inidb *db) {
++    pa_assert(db);
++
++    if (db->time_event) {
++        db->core->mainloop->time_free(db->time_event);
++        save(db);
++    }
++
++    if (db->tables)
++        pa_hashmap_free(db->tables);
++
++    pa_xfree(db->tmp_file_path);
++    pa_xfree(db->file_path);
++    pa_xfree(db->name);
++    pa_xfree(db);
++}
++
++void *pa_inidb_get_userdata(pa_inidb *db) {
++    pa_assert(db);
++
++    return db->userdata;
++}
++
++pa_inidb_table *pa_inidb_add_table(pa_inidb *db, const char *name, pa_inidb_get_object_cb_t get_object_cb) {
++    pa_inidb_table *table;
++
++    pa_assert(db);
++    pa_assert(name);
++    pa_assert(get_object_cb);
++
++    table = table_new(db, name, get_object_cb);
++    pa_assert_se(pa_hashmap_put(db->tables, table->name, table) >= 0);
++
++    return table;
++}
++
++void pa_inidb_load(pa_inidb *db) {
++    unsigned n_config_items;
++    pa_inidb_table *table;
++    void *state;
++    pa_config_item *config_items;
++    unsigned i;
++
++    pa_assert(db);
++
++    if (db->failed)
++        return;
++
++    n_config_items = 0;
++    PA_HASHMAP_FOREACH(table, db->tables, state)
++        n_config_items += pa_hashmap_size(table->columns);
++
++    config_items = pa_xnew0(pa_config_item, n_config_items + 1);
++
++    i = 0;
++    PA_HASHMAP_FOREACH(table, db->tables, state) {
++        struct column *column;
++        void *state2;
++
++        PA_HASHMAP_FOREACH(column, table->columns, state2) {
++            config_items[i].lvalue = column->name;
++            config_items[i].parse = parse_assignment;
++            i++;
++        }
++    }
++
++    pa_config_parse(db->file_path, NULL, config_items, NULL, db);
++    pa_xfree(config_items);
++}
++
++static void save(pa_inidb *db) {
++    FILE *f;
++    pa_inidb_table *table;
++    void *state;
++    int r;
++
++    pa_assert(db);
++
++    if (db->failed)
++        return;
++
++    f = pa_fopen_cloexec(db->tmp_file_path, "w");
++    if (!f) {
++        pa_log("pa_fopen_cloexec() failed: %s", pa_cstrerror(errno));
++        goto fail;
++    }
++
++    PA_HASHMAP_FOREACH(table, db->tables, state) {
++        pa_inidb_row *row;
++        void *state2;
++
++        PA_HASHMAP_FOREACH(row, table->rows, state2) {
++            size_t len;
++            size_t items_written;
++            pa_inidb_cell *cell;
++            void *state3;
++
++            len = strlen(row->header);
++            items_written = fwrite(row->header, len, 1, f);
++
++            if (items_written != 1) {
++                pa_log("fwrite() failed: %s", pa_cstrerror(errno));
++                goto fail;
++            }
++
++            PA_HASHMAP_FOREACH(cell, row->cells, state3) {
++                if (!cell->assignment)
++                    continue;
++
++                len = strlen(cell->assignment);
++                items_written = fwrite(cell->assignment, len, 1, f);
++
++                if (items_written != 1) {
++                    pa_log("fwrite() failed: %s", pa_cstrerror(errno));
++                    goto fail;
++                }
++            }
++
++            items_written = fwrite("\n", 1, 1, f);
++
++            if (items_written != 1) {
++                pa_log("fwrite() failed: %s", pa_cstrerror(errno));
++                goto fail;
++            }
++        }
++    }
++
++    r = fclose(f);
++    if (r < 0) {
++        pa_log("fclose() failed: %s", pa_cstrerror(errno));
++        goto fail;
++    }
++
++    r = rename(db->tmp_file_path, db->file_path);
++    if (r < 0) {
++        pa_log("rename() failed: %s", pa_cstrerror(errno));
++        goto fail;
++    }
++
++    pa_log_debug("Database \"%s\" saved.", db->name);
++
++    return;
++
++fail:
++    if (f)
++        fclose(f);
++
++    db->failed = true;
++    pa_log("Saving database \"%s\" failed, current and future database changes will not be written to the disk.", db->name);
++}
++
++static pa_inidb_table *table_new(pa_inidb *db, const char *name, pa_inidb_get_object_cb_t get_object_cb) {
++    pa_inidb_table *table;
++
++    pa_assert(db);
++    pa_assert(name);
++    pa_assert(get_object_cb);
++
++    table = pa_xnew0(pa_inidb_table, 1);
++    table->db = db;
++    table->name = pa_xstrdup(name);
++    table->columns = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
++                                         (pa_free_cb_t) column_free);
++    table->rows = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
++                                      (pa_free_cb_t) row_free);
++    table->get_object = get_object_cb;
++
++    return table;
++}
++
++static void table_free(pa_inidb_table *table) {
++    pa_assert(table);
++
++    if (table->rows)
++        pa_hashmap_free(table->rows);
++
++    if (table->columns)
++        pa_hashmap_free(table->columns);
++
++    pa_xfree(table->name);
++    pa_xfree(table);
++}
++
++void pa_inidb_table_add_column(pa_inidb_table *table, const char *name, pa_inidb_parse_cb_t parse_cb) {
++    struct column *column;
++
++    pa_assert(table);
++    pa_assert(name);
++    pa_assert(parse_cb);
++
++    column = column_new(name, parse_cb);
++    pa_assert_se(pa_hashmap_put(table->columns, column->name, column) >= 0);
++}
++
++static pa_inidb_row *table_add_row_internal(pa_inidb_table *table, const char *row_id) {
++    pa_inidb_row *row;
++
++    pa_assert(table);
++    pa_assert(row_id);
++
++    row = row_new(table, row_id);
++    pa_assert_se(pa_hashmap_put(table->rows, row->id, row) >= 0);
++
++    return row;
++}
++
++static void time_cb(pa_mainloop_api *api, pa_time_event *event, const struct timeval *tv, void *userdata) {
++    pa_inidb *db = userdata;
++
++    pa_assert(api);
++    pa_assert(db);
++
++    api->time_free(event);
++    db->time_event = NULL;
++
++    save(db);
++}
++
++static void trigger_save(pa_inidb *db) {
++    struct timeval tv;
++
++    pa_assert(db);
++
++    if (db->time_event)
++        return;
++
++    pa_timeval_rtstore(&tv, pa_rtclock_now() + SAVE_INTERVAL_USEC, true);
++    db->time_event = db->core->mainloop->time_new(db->core->mainloop, &tv, time_cb, db);
++}
++
++pa_inidb_row *pa_inidb_table_add_row(pa_inidb_table *table, const char *row_id) {
++    pa_inidb_row *row;
++
++    pa_assert(table);
++    pa_assert(row_id);
++
++    row = pa_hashmap_get(table->rows, row_id);
++    if (row)
++        return row;
++
++    row = table_add_row_internal(table, row_id);
++    trigger_save(table->db);
++
++    return row;
++}
++
++static struct column *column_new(const char *name, pa_inidb_parse_cb_t parse_cb) {
++    struct column *column;
++
++    pa_assert(name);
++    pa_assert(parse_cb);
++
++    column = pa_xnew(struct column, 1);
++    column->name = pa_xstrdup(name);
++    column->parse = parse_cb;
++
++    return column;
++}
++
++static void column_free(struct column *column) {
++    pa_assert(column);
++
++    pa_xfree(column->name);
++    pa_xfree(column);
++}
++
++static pa_inidb_row *row_new(pa_inidb_table *table, const char *id) {
++    pa_inidb_row *row;
++    struct column *column;
++    void *state;
++
++    pa_assert(table);
++    pa_assert(id);
++
++    row = pa_xnew0(pa_inidb_row, 1);
++    row->id = pa_xstrdup(id);
++    row->header = pa_sprintf_malloc("[%s %s]\n", table->name, id);
++    row->cells = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
++                                     (pa_free_cb_t) cell_free);
++
++    PA_HASHMAP_FOREACH(column, table->columns, state) {
++        pa_inidb_cell *cell;
++
++        cell = cell_new(table->db, column);
++        pa_hashmap_put(row->cells, cell->column->name, cell);
++    }
++
++    return row;
++}
++
++static void row_free(pa_inidb_row *row) {
++    pa_assert(row);
++
++    pa_xfree(row->header);
++    pa_xfree(row->id);
++    pa_xfree(row);
++}
++
++pa_inidb_cell *pa_inidb_row_get_cell(pa_inidb_row *row, const char *column_name) {
++    pa_inidb_cell *cell;
++
++    pa_assert(row);
++    pa_assert(column_name);
++
++    pa_assert_se(cell = pa_hashmap_get(row->cells, column_name));
++
++    return cell;
++}
++
++static pa_inidb_cell *cell_new(pa_inidb *db, struct column *column) {
++    pa_inidb_cell *cell;
++
++    pa_assert(db);
++    pa_assert(column);
++
++    cell = pa_xnew0(pa_inidb_cell, 1);
++    cell->db = db;
++    cell->column = column;
++
++    return cell;
++}
++
++static void cell_free(pa_inidb_cell *cell) {
++    pa_assert(cell);
++
++    pa_xfree(cell->assignment);
++    pa_xfree(cell->value);
++    pa_xfree(cell);
++}
++
++static void cell_set_value_internal(pa_inidb_cell *cell, const char *value) {
++    pa_assert(cell);
++    pa_assert(value);
++
++    pa_xfree(cell->value);
++    cell->value = pa_xstrdup(value);
++
++    pa_xfree(cell->assignment);
++    if (value)
++        cell->assignment = pa_sprintf_malloc("%s = %s\n", cell->column->name, value);
++    else
++        cell->assignment = NULL;
++}
++
++void pa_inidb_cell_set_value(pa_inidb_cell *cell, const char *value) {
++    pa_assert(cell);
++
++    if (pa_safe_streq(value, cell->value))
++        return;
++
++    cell_set_value_internal(cell, value);
++    trigger_save(cell->db);
++}
+diff --git a/src/modules/volume-api/inidb.h b/src/modules/volume-api/inidb.h
+new file mode 100644
+index 0000000..ded73ba
+--- /dev/null
++++ b/src/modules/volume-api/inidb.h
+@@ -0,0 +1,54 @@
++#ifndef fooinidbhfoo
++#define fooinidbhfoo
++
++/***
++  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_inidb pa_inidb;
++typedef struct pa_inidb_cell pa_inidb_cell;
++typedef struct pa_inidb_row pa_inidb_row;
++typedef struct pa_inidb_table pa_inidb_table;
++
++/* If there's no object with the given name, the implementation is expected to
++ * create a new object (or at least try to). */
++typedef int (*pa_inidb_get_object_cb_t)(pa_inidb *db, const char *name, void **_r);
++
++/* The implementation is expected to parse the value, and set the parsed value
++ * on the object. */
++typedef int (*pa_inidb_parse_cb_t)(pa_inidb *db, const char *value, void *object);
++
++pa_inidb *pa_inidb_new(pa_core *core, const char *name, void *userdata);
++void pa_inidb_free(pa_inidb *db);
++
++void *pa_inidb_get_userdata(pa_inidb *db);
++pa_inidb_table *pa_inidb_add_table(pa_inidb *db, const char *name, pa_inidb_get_object_cb_t get_object_cb);
++void pa_inidb_load(pa_inidb *db);
++
++void pa_inidb_table_add_column(pa_inidb_table *table, const char *name, pa_inidb_parse_cb_t parse_cb);
++pa_inidb_row *pa_inidb_table_add_row(pa_inidb_table *table, const char *row_id);
++
++pa_inidb_cell *pa_inidb_row_get_cell(pa_inidb_row *row, const char *column_name);
++
++void pa_inidb_cell_set_value(pa_inidb_cell *cell, const char *value);
++
++#endif
+diff --git a/src/modules/volume-api/module-volume-api.c b/src/modules/volume-api/module-volume-api.c
+index 845ac09..7b112f6 100644
+--- a/src/modules/volume-api/module-volume-api.c
++++ b/src/modules/volume-api/module-volume-api.c
+@@ -51,6 +51,7 @@ struct userdata {
+     pa_hook_slot *volume_control_unlink_slot;
+     pa_hook_slot *volume_control_description_changed_slot;
+     pa_hook_slot *volume_control_volume_changed_slot;
++    pa_hook_slot *volume_control_convertible_to_db_changed_slot;
+     pa_hook_slot *mute_control_put_slot;
+     pa_hook_slot *mute_control_unlink_slot;
+     pa_hook_slot *mute_control_description_changed_slot;
+@@ -63,10 +64,13 @@ struct userdata {
+     pa_hook_slot *stream_put_slot;
+     pa_hook_slot *stream_unlink_slot;
+     pa_hook_slot *stream_description_changed_slot;
++    pa_hook_slot *stream_proplist_changed_slot;
+     pa_hook_slot *stream_volume_control_changed_slot;
++    pa_hook_slot *stream_relative_volume_control_changed_slot;
+     pa_hook_slot *stream_mute_control_changed_slot;
+     pa_hook_slot *audio_group_put_slot;
+     pa_hook_slot *audio_group_unlink_slot;
++    pa_hook_slot *audio_group_description_changed_slot;
+     pa_hook_slot *audio_group_volume_control_changed_slot;
+     pa_hook_slot *audio_group_mute_control_changed_slot;
+     pa_hook_slot *main_output_volume_control_changed_slot;
+@@ -1247,6 +1251,9 @@ int pa__init(pa_module *module) {
+     u->volume_control_volume_changed_slot =
+             pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_VOLUME_CHANGED],
+                             PA_HOOK_NORMAL, volume_control_event_cb, u);
++    u->volume_control_convertible_to_db_changed_slot =
++            pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_CONVERTIBLE_TO_DB_CHANGED], PA_HOOK_NORMAL,
++                            volume_control_event_cb, u);
+     u->mute_control_put_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_PUT],
+                                                PA_HOOK_NORMAL, mute_control_put_cb, u);
+     u->mute_control_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK],
+@@ -1277,9 +1284,14 @@ int pa__init(pa_module *module) {
+     u->stream_description_changed_slot =
+             pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_DESCRIPTION_CHANGED], PA_HOOK_NORMAL,
+                             stream_event_cb, u);
++    u->stream_proplist_changed_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_PROPLIST_CHANGED],
++                                                      PA_HOOK_NORMAL, stream_event_cb, u);
+     u->stream_volume_control_changed_slot =
+             pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED],
+                             PA_HOOK_NORMAL, stream_event_cb, u);
++    u->stream_relative_volume_control_changed_slot =
++            pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_RELATIVE_VOLUME_CONTROL_CHANGED],
++                            PA_HOOK_NORMAL, stream_event_cb, u);
+     u->stream_mute_control_changed_slot =
+             pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_MUTE_CONTROL_CHANGED], PA_HOOK_NORMAL,
+                             stream_event_cb, u);
+@@ -1287,6 +1299,9 @@ int pa__init(pa_module *module) {
+                                               PA_HOOK_NORMAL, audio_group_put_cb, u);
+     u->audio_group_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK],
+                                                  PA_HOOK_NORMAL, audio_group_unlink_cb, u);
++    u->audio_group_description_changed_slot =
++            pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_DESCRIPTION_CHANGED], PA_HOOK_NORMAL,
++                            audio_group_event_cb, u);
+     u->audio_group_volume_control_changed_slot =
+             pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_VOLUME_CONTROL_CHANGED],
+                             PA_HOOK_NORMAL, audio_group_event_cb, u);
+@@ -1352,6 +1367,9 @@ void pa__done(pa_module *module) {
+     if (u->audio_group_volume_control_changed_slot)
+         pa_hook_slot_free(u->audio_group_volume_control_changed_slot);
++    if (u->audio_group_description_changed_slot)
++        pa_hook_slot_free(u->audio_group_description_changed_slot);
++
+     if (u->audio_group_unlink_slot)
+         pa_hook_slot_free(u->audio_group_unlink_slot);
+@@ -1361,9 +1379,15 @@ void pa__done(pa_module *module) {
+     if (u->stream_mute_control_changed_slot)
+         pa_hook_slot_free(u->stream_mute_control_changed_slot);
++    if (u->stream_relative_volume_control_changed_slot)
++        pa_hook_slot_free(u->stream_relative_volume_control_changed_slot);
++
+     if (u->stream_volume_control_changed_slot)
+         pa_hook_slot_free(u->stream_volume_control_changed_slot);
++    if (u->stream_proplist_changed_slot)
++        pa_hook_slot_free(u->stream_proplist_changed_slot);
++
+     if (u->stream_description_changed_slot)
+         pa_hook_slot_free(u->stream_description_changed_slot);
+@@ -1400,6 +1424,9 @@ void pa__done(pa_module *module) {
+     if (u->mute_control_put_slot)
+         pa_hook_slot_free(u->mute_control_put_slot);
++    if (u->volume_control_convertible_to_db_changed_slot)
++        pa_hook_slot_free(u->volume_control_convertible_to_db_changed_slot);
++
+     if (u->volume_control_volume_changed_slot)
+         pa_hook_slot_free(u->volume_control_volume_changed_slot);
+diff --git a/src/modules/volume-api/mute-control.c b/src/modules/volume-api/mute-control.c
+index adc008e..1b2f276 100644
+--- a/src/modules/volume-api/mute-control.c
++++ b/src/modules/volume-api/mute-control.c
+@@ -31,52 +31,73 @@
+ #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;
++int pa_mute_control_new(pa_volume_api *api, const char *name, bool persistent, pa_mute_control **_r) {
++    pa_mute_control *control = NULL;
++    int r;
+     pa_assert(api);
+     pa_assert(name);
+-    pa_assert(description);
++    pa_assert(_r);
+     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);
++
++    r = pa_volume_api_register_name(api, name, false, &control->name);
++    if (r < 0)
++        goto fail;
++
++    control->description = pa_xstrdup(control->name);
+     control->proplist = pa_proplist_new();
++    control->present = !persistent;
++    control->persistent = persistent;
++    control->purpose = PA_MUTE_CONTROL_PURPOSE_OTHER;
+     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;
++    if (persistent) {
++        pa_inidb_row *row;
++
++        row = pa_inidb_table_add_row(api->control_db.mute_controls, control->name);
++        control->db_cells.description = pa_inidb_row_get_cell(row, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_DESCRIPTION);
++        control->db_cells.mute = pa_inidb_row_get_cell(row, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_MUTE);
++    }
++
++    *_r = control;
++    return 0;
++
++fail:
++    if (control)
++        pa_mute_control_free(control);
++
++    return r;
+ }
+-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_put(pa_mute_control *control) {
+     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);
++    pa_assert(control->set_mute || !control->present);
+-    if (initial_mute_is_set)
+-        control->mute = initial_mute;
+-    else
+-        control->mute = false;
++    pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_IMPLEMENTATION_INITIALIZED], control);
++    pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_SET_INITIAL_MUTE], control);
+-    if (set_initial_mute_cb)
+-        set_initial_mute_cb(control);
++    if (control->set_mute) {
++        control->set_mute_in_progress = true;
++        control->set_mute(control, control->mute);
++        control->set_mute_in_progress = false;
++    }
+     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("    Present: %s", pa_yes_no(control->present));
++    pa_log_debug("    Persistent: %s", pa_yes_no(control->persistent));
+     pa_log_debug("    Properties:");
+     while ((prop_key = pa_proplist_iterate(control->proplist, &state)))
+@@ -86,9 +107,7 @@ void pa_mute_control_put(pa_mute_control *control, bool initial_mute, bool initi
+ }
+ void pa_mute_control_unlink(pa_mute_control *control) {
+-    pa_audio_group *group;
+     pa_device *device;
+-    pas_stream *stream;
+     pa_assert(control);
+@@ -102,15 +121,9 @@ void pa_mute_control_unlink(pa_mute_control *control) {
+     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);
+-    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);
++    pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK], control);
+     while ((device = pa_hashmap_first(control->default_for_devices)))
+         pa_device_set_default_mute_control(device, NULL);
+@@ -133,19 +146,10 @@ void pa_mute_control_unlink(pa_mute_control *control) {
+ void pa_mute_control_free(pa_mute_control *control) {
+     pa_assert(control);
+-    if (!control->unlinked)
++    /* unlink() expects name to be set. */
++    if (!control->unlinked && control->name)
+         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);
+@@ -167,86 +171,137 @@ void pa_mute_control_free(pa_mute_control *control) {
+     pa_xfree(control);
+ }
+-void pa_mute_control_set_owner_audio_group(pa_mute_control *control, pa_audio_group *group) {
++void pa_mute_control_set_purpose(pa_mute_control *control, pa_mute_control_purpose_t purpose, void *owner) {
+     pa_assert(control);
+-    pa_assert(group);
++    pa_assert(!control->linked);
+-    control->owner_audio_group = group;
++    control->purpose = purpose;
++    control->owner = owner;
+ }
+-static void set_mute_internal(pa_mute_control *control, bool mute) {
+-    bool old_mute;
+-
++int pa_mute_control_acquire_for_audio_group(pa_mute_control *control, pa_audio_group *group,
++                                            pa_mute_control_set_mute_cb_t set_mute_cb, void *userdata) {
+     pa_assert(control);
++    pa_assert(group);
++    pa_assert(set_mute_cb);
+-    old_mute = control->mute;
++    if (control->present) {
++        pa_log("Can't acquire mute control %s, it's already present.", control->name);
++        return -PA_ERR_BUSY;
++    }
+-    if (mute == old_mute)
+-        return;
++    control->owner_audio_group = group;
++    control->set_mute = set_mute_cb;
++    control->userdata = userdata;
+-    control->mute = mute;
++    control->set_mute_in_progress = true;
++    control->set_mute(control, control->mute);
++    control->set_mute_in_progress = false;
++
++    control->present = true;
+     if (!control->linked || control->unlinked)
+-        return;
++        return 0;
+-    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_log_debug("Mute control %s became present.", control->name);
+-    pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_MUTE_CHANGED], control);
++    return 0;
+ }
+-int pa_mute_control_set_mute(pa_mute_control *control, bool mute) {
+-    int r;
+-
++void pa_mute_control_release(pa_mute_control *control) {
+     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 (!control->present)
++        return;
+-    if (mute == control->mute)
+-        return 0;
++    control->present = false;
+-    control->set_mute_in_progress = true;
+-    r = control->set_mute(control, mute);
+-    control->set_mute_in_progress = false;
++    control->userdata = NULL;
++    control->set_mute = NULL;
++    control->owner_audio_group = NULL;
+-    if (r >= 0)
+-        set_mute_internal(control, mute);
++    if (!control->linked || control->unlinked)
++        return;
+-    return r;
++    pa_log_debug("Mute control %s became not present.", control->name);
+ }
+-void pa_mute_control_description_changed(pa_mute_control *control, const char *new_description) {
++void pa_mute_control_set_description(pa_mute_control *control, const char *description) {
+     char *old_description;
+     pa_assert(control);
+-    pa_assert(new_description);
++    pa_assert(description);
+     old_description = control->description;
+-    if (pa_streq(new_description, old_description))
++    if (pa_streq(description, old_description))
++        return;
++
++    control->description = pa_xstrdup(description);
++
++    if (control->persistent)
++        pa_inidb_cell_set_value(control->db_cells.description, description);
++
++    if (!control->linked || control->unlinked) {
++        pa_xfree(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);
++                 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) {
++static void set_mute_internal(pa_mute_control *control, bool mute) {
++    bool old_mute;
++
+     pa_assert(control);
+-    if (!control->linked)
++    old_mute = control->mute;
++
++    if (mute == old_mute)
+         return;
+-    if (control->set_mute_in_progress)
++    control->mute = mute;
++
++    if (control->persistent)
++        pa_inidb_cell_set_value(control->db_cells.mute, pa_boolean_to_string(mute));
++
++    if (!control->linked || control->unlinked)
+         return;
+-    set_mute_internal(control, new_mute);
++    pa_log_debug("The mute of mute control %s changed from %s to %s.", control->name, pa_boolean_to_string(old_mute),
++                 pa_boolean_to_string(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_in_progress)
++        return 0;
++
++    if (mute == control->mute)
++        return 0;
++
++    if (control->linked && control->present) {
++        control->set_mute_in_progress = true;
++        r = control->set_mute(control, mute);
++        control->set_mute_in_progress = false;
++
++        if (r < 0) {
++            pa_log("Setting the mute of mute control %s failed.", control->name);
++            return r;
++        }
++    }
++
++    set_mute_internal(control, mute);
++
++    return 0;
+ }
+ void pa_mute_control_add_device(pa_mute_control *control, pa_device *device) {
+@@ -276,31 +331,3 @@ void pa_mute_control_remove_default_for_device(pa_mute_control *control, pa_devi
+     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
+index 1f70a43..40f8a9c 100644
+--- a/src/modules/volume-api/mute-control.h
++++ b/src/modules/volume-api/mute-control.h
+@@ -22,10 +22,18 @@
+   USA.
+ ***/
++#include <modules/volume-api/inidb.h>
+ #include <modules/volume-api/volume-api.h>
+ typedef struct pa_mute_control pa_mute_control;
++typedef enum {
++    PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE,
++    PA_MUTE_CONTROL_PURPOSE_OTHER,
++} pa_mute_control_purpose_t;
++
++typedef int (*pa_mute_control_set_mute_cb_t)(pa_mute_control *control, bool mute);
++
+ struct pa_mute_control {
+     pa_volume_api *volume_api;
+     uint32_t index;
+@@ -33,6 +41,14 @@ struct pa_mute_control {
+     char *description;
+     pa_proplist *proplist;
+     bool mute;
++    bool present;
++    bool persistent;
++
++    pa_mute_control_purpose_t purpose;
++    union {
++        pas_stream *owner_stream;
++        void *owner;
++    };
+     /* 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. */
+@@ -40,50 +56,43 @@ struct pa_mute_control {
+     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) */
++
++    struct {
++        pa_inidb_cell *description;
++        pa_inidb_cell *mute;
++    } db_cells;
+     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);
++     * to return a negative error code on failure. */
++    pa_mute_control_set_mute_cb_t set_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);
+-
++int pa_mute_control_new(pa_volume_api *api, const char *name, bool persistent, pa_mute_control **_r);
++void pa_mute_control_put(pa_mute_control *control);
+ 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, before pa_mute_control_put(). */
++void pa_mute_control_set_purpose(pa_mute_control *control, pa_mute_control_purpose_t purpose, void *owner);
+ /* 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);
++int pa_mute_control_acquire_for_audio_group(pa_mute_control *control, pa_audio_group *group,
++                                            pa_mute_control_set_mute_cb_t set_mute_cb, void *userdata);
++
++/* Called by the mute control implementation. This must only be called for
++ * persistent controls; use pa_mute_control_free() for non-persistent
++ * controls. */
++void pa_mute_control_release(pa_mute_control *control);
++
++/* Called by anyone. */
++void pa_mute_control_set_description(pa_mute_control *control, const char *description);
++int pa_mute_control_set_mute(pa_mute_control *control, bool mute);
+ /* Called from device.c only. */
+ void pa_mute_control_add_device(pa_mute_control *control, pa_device *device);
+@@ -91,12 +100,4 @@ 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
+index e3531a8..1738d15 100644
+--- a/src/modules/volume-api/sstream.c
++++ b/src/modules/volume-api/sstream.c
+@@ -26,7 +26,6 @@
+ #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>
+@@ -34,121 +33,43 @@
+ #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;
++int pas_stream_new(pa_volume_api *api, const char *name, pas_stream **_r) {
++    pas_stream *stream = NULL;
++    int r;
+     pa_assert(api);
+     pa_assert(name);
+-    pa_assert(description);
++    pa_assert(_r);
+     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;
++    r = pa_volume_api_register_name(api, name, false, &stream->name);
++    if (r < 0)
++        goto fail;
+-    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);
+-    }
++    stream->description = pa_xstrdup(stream->name);
++    stream->direction = PA_DIRECTION_OUTPUT;
++    stream->proplist = pa_proplist_new();
+-    if (!stream->linked || stream->unlinked)
+-        return;
++    *_r = stream;
++    return 0;
+-    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)");
++fail:
++    if (stream)
++        pas_stream_free(stream);
+-    pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_MUTE_CONTROL_CHANGED], stream);
++    return r;
+ }
+-void pas_stream_put(pas_stream *stream, pa_proplist *initial_properties) {
++void pas_stream_put(pas_stream *stream) {
+     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);
+@@ -157,6 +78,10 @@ void pas_stream_put(pas_stream *stream, pa_proplist *initial_properties) {
+     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("    Audio group for volume: %s",
++                 stream->audio_group_for_volume ? stream->audio_group_for_volume->name : "(unset)");
++    pa_log_debug("    Audio group for mute: %s",
++                 stream->audio_group_for_mute ? stream->audio_group_for_mute->name : "(unset)");
+     pa_log_debug("    Properties:");
+     while ((prop_key = pa_proplist_iterate(stream->proplist, &state)))
+@@ -178,22 +103,22 @@ void pas_stream_unlink(pas_stream *stream) {
+     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);
+-    pa_volume_api_remove_stream(stream->volume_api, stream);
++    pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_UNLINK], 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_relative_volume_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)
++    /* unlink() expects name to be set. */
++    if (!stream->unlinked && stream->name)
+         pas_stream_unlink(stream);
+     if (stream->proplist)
+@@ -207,160 +132,172 @@ void pas_stream_free(pas_stream *stream) {
+     pa_xfree(stream);
+ }
+-int pas_stream_set_have_own_volume_control(pas_stream *stream, bool have) {
++void pas_stream_set_direction(pas_stream *stream, pa_direction_t direction) {
+     pa_assert(stream);
++    pa_assert(!stream->linked);
+-    if (have == stream->have_own_volume_control)
+-        return 0;
++    stream->direction = direction;
++}
+-    if (have) {
+-        pa_assert(!stream->own_volume_control);
++void pas_stream_set_description(pas_stream *stream, const char *description) {
++    char *old_description;
++
++    pa_assert(stream);
++    pa_assert(description);
+-        if (!stream->create_own_volume_control) {
+-            pa_log_debug("Stream %s doesn't support own volume control.", stream->name);
+-            return -PA_ERR_NOTSUPPORTED;
+-        }
++    old_description = stream->description;
++
++    if (pa_streq(description, old_description))
++        return;
+-        stream->own_volume_control = stream->create_own_volume_control(stream);
+-    } else {
+-        stream->delete_own_volume_control(stream);
+-        stream->own_volume_control = NULL;
++    stream->description = pa_xstrdup(description);
++
++    if (!stream->linked || stream->unlinked) {
++        pa_xfree(old_description);
++        return;
+     }
+-    stream->have_own_volume_control = have;
++    pa_log_debug("Stream %s description changed from \"%s\" to \"%s\".", stream->name, old_description,
++                 description);
++    pa_xfree(old_description);
+-    return 0;
++    pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_DESCRIPTION_CHANGED], stream);
+ }
+-int pas_stream_set_have_own_mute_control(pas_stream *stream, bool have) {
++void pas_stream_set_property(pas_stream *stream, const char *key, const char *value) {
++    const char *old_value;
++
+     pa_assert(stream);
++    pa_assert(key);
+-    if (have == stream->have_own_mute_control)
+-        return 0;
++    old_value = pa_proplist_gets(stream->proplist, key);
+-    if (have) {
+-        pa_assert(!stream->own_mute_control);
++    if (pa_safe_streq(value, old_value))
++        return;
+-        if (!stream->create_own_mute_control) {
+-            pa_log_debug("Stream %s doesn't support own mute control.", stream->name);
+-            return -PA_ERR_NOTSUPPORTED;
+-        }
++    if (value)
++        pa_proplist_sets(stream->proplist, key, value);
++    else
++        pa_proplist_unset(stream->proplist, key);
+-        stream->own_mute_control = stream->create_own_mute_control(stream);
+-    } else {
+-        stream->delete_own_mute_control(stream);
+-        stream->own_mute_control = NULL;
+-    }
++    if (!stream->linked || stream->unlinked)
++        return;
+-    stream->have_own_mute_control = have;
++    pa_log_debug("Stream %s property \"%s\" changed from \"%s\" to \"%s\".", stream->name, key,
++                 old_value ? old_value : "(unset)", value ? value : "(unset)");
+-    return 0;
++    pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_PROPLIST_CHANGED], stream);
+ }
+ void pas_stream_set_volume_control(pas_stream *stream, pa_volume_control *control) {
+-    pa_assert(stream);
++    pa_volume_control *old_control;
+-    stream->use_default_volume_control = false;
++    pa_assert(stream);
+-    if (stream->volume_control_binding) {
+-        pa_binding_free(stream->volume_control_binding);
+-        stream->volume_control_binding = NULL;
+-    }
++    old_control = stream->volume_control;
+-    set_volume_control_internal(stream, control);
+-}
++    if (control == old_control)
++        return;
+-void pas_stream_set_mute_control(pas_stream *stream, pa_mute_control *control) {
+-    pa_assert(stream);
++    stream->volume_control = control;
+-    stream->use_default_mute_control = false;
++    if (!stream->linked || stream->unlinked)
++        return;
+-    if (stream->mute_control_binding) {
+-        pa_binding_free(stream->mute_control_binding);
+-        stream->mute_control_binding = NULL;
+-    }
++    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)");
+-    set_mute_control_internal(stream, control);
++    pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED], stream);
+ }
+-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,
+-    };
++void pas_stream_set_relative_volume_control(pas_stream *stream, pa_volume_control *control) {
++    pa_volume_control *old_control;
+     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);
++    old_control = stream->relative_volume_control;
+-    stream->volume_control_binding = pa_binding_new(stream->volume_api, &owner_info, target_info);
+-}
++    if (control == old_control)
++        return;
+-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,
+-    };
++    stream->relative_volume_control = control;
+-    pa_assert(stream);
+-    pa_assert(target_info);
+-
+-    stream->use_default_mute_control = false;
++    if (!stream->linked || stream->unlinked)
++        return;
+-    if (stream->mute_control_binding)
+-        pa_binding_free(stream->mute_control_binding);
++    pa_log_debug("The relative volume control of stream %s changed from %s to %s.", stream->name,
++                 old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
+-    stream->mute_control_binding = pa_binding_new(stream->volume_api, &owner_info, target_info);
++    pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_RELATIVE_VOLUME_CONTROL_CHANGED], stream);
+ }
+-void pas_stream_description_changed(pas_stream *stream, const char *new_description) {
+-    char *old_description;
++void pas_stream_set_mute_control(pas_stream *stream, pa_mute_control *control) {
++    pa_mute_control *old_control;
+     pa_assert(stream);
+-    pa_assert(new_description);
+-    old_description = stream->description;
++    old_control = stream->mute_control;
+-    if (pa_streq(new_description, old_description))
++    if (control == old_control)
+         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);
++    stream->mute_control = control;
++
++    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_set_audio_group_for_volume(pas_stream *stream, pa_audio_group *group) {
++    pa_audio_group *old_group;
++
+     pa_assert(stream);
+-    if (group == stream->audio_group_for_volume)
++    old_group = stream->audio_group_for_volume;
++
++    if (group == old_group)
+         return;
+-    if (stream->audio_group_for_volume)
+-        pa_audio_group_remove_volume_stream(stream->audio_group_for_volume, stream);
++    if (old_group)
++        pa_audio_group_remove_volume_stream(old_group, stream);
+     stream->audio_group_for_volume = group;
+     if (group)
+         pa_audio_group_add_volume_stream(group, stream);
++
++    if (!stream->linked || stream->unlinked)
++        return;
++
++    pa_log_debug("Stream %s audio group for volume changed from %s to %s.", stream->name,
++                 old_group ? old_group->name : "(unset)", group ? group->name : "(unset)");
+ }
+ void pas_stream_set_audio_group_for_mute(pas_stream *stream, pa_audio_group *group) {
++    pa_audio_group *old_group;
++
+     pa_assert(stream);
+-    if (group == stream->audio_group_for_mute)
++    old_group = stream->audio_group_for_mute;
++
++    if (group == old_group)
+         return;
+-    if (stream->audio_group_for_mute)
+-        pa_audio_group_remove_mute_stream(stream->audio_group_for_mute, stream);
++    if (old_group)
++        pa_audio_group_remove_mute_stream(old_group, stream);
+     stream->audio_group_for_mute = group;
+     if (group)
+         pa_audio_group_add_mute_stream(group, stream);
++
++    if (!stream->linked || stream->unlinked)
++        return;
++
++    pa_log_debug("Stream %s audio group for mute changed from %s to %s.", stream->name,
++                 old_group ? old_group->name : "(unset)", group ? group->name : "(unset)");
+ }
+diff --git a/src/modules/volume-api/sstream.h b/src/modules/volume-api/sstream.h
+index a65b34c..715bf2c 100644
+--- a/src/modules/volume-api/sstream.h
++++ b/src/modules/volume-api/sstream.h
+@@ -39,69 +39,33 @@ struct pas_stream {
+     pa_direction_t direction;
+     pa_proplist *proplist;
+     pa_volume_control *volume_control;
++    pa_volume_control *relative_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);
++int pas_stream_new(pa_volume_api *api, const char *name, pas_stream **_r);
++void pas_stream_put(pas_stream *stream);
+ 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 the stream implementation, only during initialization. */
++void pas_stream_set_direction(pas_stream *stream, pa_direction_t direction);
+-/* Called by policy modules. */
++/* Called by the stream implementation. */
++void pas_stream_set_description(pas_stream *stream, const char *description);
++void pas_stream_set_property(pas_stream *stream, const char *key, const char *value);
+ void pas_stream_set_volume_control(pas_stream *stream, pa_volume_control *control);
++void pas_stream_set_relative_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. */
++/* Called by anyone. */
+ 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);
+diff --git a/src/modules/volume-api/stream-creator.c b/src/modules/volume-api/stream-creator.c
+index f6ca7b3..0d9ea24 100644
+--- a/src/modules/volume-api/stream-creator.c
++++ b/src/modules/volume-api/stream-creator.c
+@@ -35,9 +35,9 @@
+ 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_fixate_slot;
+     pa_hook_slot *sink_input_unlink_slot;
+-    pa_hook_slot *source_output_put_slot;
++    pa_hook_slot *source_output_fixate_slot;
+     pa_hook_slot *source_output_unlink_slot;
+ };
+@@ -47,73 +47,61 @@ enum stream_type {
+ };
+ struct stream {
++    pa_core *core;
+     pa_stream_creator *creator;
+     enum stream_type type;
++    pa_sink_input_new_data *sink_input_new_data;
+     pa_sink_input *sink_input;
++    pa_source_output_new_data *source_output_new_data;
+     pa_source_output *source_output;
+     pa_client *client;
++    pa_volume_control *volume_control;
++    pa_volume_control *relative_volume_control;
++    pa_mute_control *mute_control;
+     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 *reference_ratio_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 void stream_free(struct stream *stream);
+-static int volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance) {
++static int volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *original_volume,
++                                        const pa_bvolume *remapped_volume, bool set_volume, bool set_balance) {
+     struct stream *stream;
+     pa_bvolume bvolume;
+     pa_cvolume cvolume;
+     pa_assert(control);
+-    pa_assert(volume);
++    pa_assert(original_volume);
++    pa_assert(remapped_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;
+-    }
++    bvolume = control->volume;
+     if (set_volume)
+-        bvolume.volume = volume->volume;
++        bvolume.volume = remapped_volume->volume;
+     if (set_balance)
+-        pa_bvolume_copy_balance(&bvolume, volume);
++        pa_bvolume_copy_balance(&bvolume, remapped_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);
++            if (stream->sink_input->state == PA_SINK_INPUT_INIT)
++                pa_sink_input_new_data_set_volume(stream->sink_input_new_data, &cvolume, false);
++            else
++                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);
++            if (stream->source_output->state == PA_SOURCE_OUTPUT_INIT)
++                pa_source_output_new_data_set_volume(stream->source_output_new_data, &cvolume, false);
++            else
++                pa_source_output_set_volume(stream->source_output, &cvolume, true, true);
+             break;
+     }
+@@ -129,6 +117,9 @@ static pa_hook_result_t sink_input_or_source_output_volume_changed_cb(void *hook
+     pa_assert(stream);
+     pa_assert(call_data);
++    if (!stream->volume_control)
++        return PA_HOOK_OK;
++
+     switch (stream->type) {
+         case STREAM_TYPE_SINK_INPUT:
+             input = call_data;
+@@ -144,63 +135,95 @@ static pa_hook_result_t sink_input_or_source_output_volume_changed_cb(void *hook
+     if (input)
+         pa_bvolume_from_cvolume(&bvolume, &input->volume, &input->channel_map);
+-    else
++    else if (output)
+         pa_bvolume_from_cvolume(&bvolume, &output->volume, &output->channel_map);
++    else
++        pa_assert_not_reached();
+-    pa_volume_control_volume_changed(stream->stream->own_volume_control, &bvolume, true, true);
++    pa_volume_control_set_volume(stream->volume_control, &bvolume, true, true);
+     return PA_HOOK_OK;
+ }
+-static void volume_control_set_initial_volume_cb(pa_volume_control *control) {
++static int relative_volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *original_volume,
++                                                 const pa_bvolume *remapped_volume, bool set_volume, bool set_balance) {
+     struct stream *stream;
++    pa_bvolume bvolume;
+     pa_cvolume cvolume;
+     pa_assert(control);
++    pa_assert(original_volume);
++    pa_assert(remapped_volume);
+     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;
++    bvolume = control->volume;
+-        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;
++    if (set_volume)
++        bvolume.volume = remapped_volume->volume;
+-    pa_assert(control);
++    if (set_balance)
++        pa_bvolume_copy_balance(&bvolume, remapped_volume);
+-    stream = control->userdata;
++    pa_bvolume_to_cvolume(&bvolume, &cvolume);
+     switch (stream->type) {
+         case STREAM_TYPE_SINK_INPUT:
+-            pa_sink_input_set_mute(stream->sink_input, mute, true);
++            if (stream->sink_input->state == PA_SINK_INPUT_INIT) {
++                pa_sink_input_new_data_set_volume(stream->sink_input_new_data, &cvolume, true);
++
++                /* XXX: This is a bit ugly. This is needed, because when we
++                 * call pa_sink_input_new_data_set_volume(), there's no
++                 * automatic notification to the primary volume control object
++                 * about the changed volume. This problem should go away once
++                 * stream volume controls are moved into the core. */
++                if (stream->volume_control) {
++                    pa_bvolume absolute_volume;
++
++                    pa_bvolume_from_cvolume(&absolute_volume, &stream->sink_input_new_data->volume,
++                                            &stream->sink_input_new_data->channel_map);
++                    pa_volume_control_set_volume(stream->volume_control, &absolute_volume, true, true);
++                }
++            } else
++                pa_sink_input_set_volume(stream->sink_input, &cvolume, true, false);
+             break;
+         case STREAM_TYPE_SOURCE_OUTPUT:
+-            pa_source_output_set_mute(stream->source_output, mute, true);
++            if (stream->source_output->state == PA_SOURCE_OUTPUT_INIT) {
++                pa_source_output_new_data_set_volume(stream->source_output_new_data, &cvolume, true);
++
++                /* XXX: This is a bit ugly. This is needed, because when we
++                 * call pa_source_output_new_data_set_volume(), there's no
++                 * automatic notification to the primary volume control object
++                 * about the changed volume. This problem should go away once
++                 * stream volume controls are moved into the core. */
++                if (stream->volume_control) {
++                    pa_bvolume absolute_volume;
++
++                    pa_bvolume_from_cvolume(&absolute_volume, &stream->source_output_new_data->volume,
++                                            &stream->source_output_new_data->channel_map);
++                    pa_volume_control_set_volume(stream->volume_control, &absolute_volume, true, true);
++                }
++            } else
++                pa_source_output_set_volume(stream->source_output, &cvolume, true, false);
+             break;
+     }
+     return 0;
+ }
+-static pa_hook_result_t sink_input_or_source_output_mute_changed_cb(void *hook_data, void *call_data, void *userdata) {
++static pa_hook_result_t sink_input_or_source_output_reference_ratio_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_bvolume bvolume;
+     pa_assert(stream);
+     pa_assert(call_data);
++    if (!stream->relative_volume_control)
++        return PA_HOOK_OK;
++
+     switch (stream->type) {
+         case STREAM_TYPE_SINK_INPUT:
+             input = call_data;
+@@ -215,18 +238,18 @@ static pa_hook_result_t sink_input_or_source_output_mute_changed_cb(void *hook_d
+         return PA_HOOK_OK;
+     if (input)
+-        mute = input->muted;
++        pa_bvolume_from_cvolume(&bvolume, &input->reference_ratio, &input->channel_map);
+     else if (output)
+-        mute = output->muted;
++        pa_bvolume_from_cvolume(&bvolume, &output->reference_ratio, &output->channel_map);
+     else
+         pa_assert_not_reached();
+-    pa_mute_control_mute_changed(stream->stream->own_mute_control, mute);
++    pa_volume_control_set_volume(stream->relative_volume_control, &bvolume, true, true);
+     return PA_HOOK_OK;
+ }
+-static void mute_control_set_initial_mute_cb(pa_mute_control *control) {
++static int mute_control_set_mute_cb(pa_mute_control *control, bool mute) {
+     struct stream *stream;
+     pa_assert(control);
+@@ -235,167 +258,66 @@ static void mute_control_set_initial_mute_cb(pa_mute_control *control) {
+     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);
++            if (stream->sink_input->state == PA_SINK_INPUT_INIT)
++                pa_sink_input_new_data_set_muted(stream->sink_input_new_data, mute);
++            else
++                pa_sink_input_set_mute(stream->sink_input, mute, true);
+             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);
++            if (stream->source_output->state == PA_SOURCE_OUTPUT_INIT)
++                pa_source_output_new_data_set_muted(stream->source_output_new_data, mute);
++            else
++                pa_source_output_set_mute(stream->source_output, mute, true);
+             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);
++    return 0;
+ }
+-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;
+-    }
++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;
+-    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);
++    pa_assert(call_data);
+-    pa_assert(!stream->mute_changed_slot);
++    if (!stream->mute_control)
++        return PA_HOOK_OK;
+     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;
++            input = call_data;
+             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;
++            output = call_data;
+             break;
+     }
+-    pa_mute_control_put(control, mute, true, mute_control_set_initial_mute_cb);
+-
+-    return control;
+-}
++    if ((input && input != stream->sink_input) || (output && output != stream->source_output))
++        return PA_HOOK_OK;
+-static void stream_delete_own_mute_control_cb(pas_stream *s) {
+-    struct stream *stream;
++    if (input)
++        mute = input->muted;
++    else if (output)
++        mute = output->muted;
++    else
++        pa_assert_not_reached();
+-    pa_assert(s);
++    pa_mute_control_set_mute(stream->mute_control, mute);
+-    stream = s->userdata;
+-    pa_hook_slot_free(stream->mute_changed_slot);
+-    stream->mute_changed_slot = NULL;
+-    pa_mute_control_free(s->own_mute_control);
++    return PA_HOOK_OK;
+ }
+ 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_proplist *proplist = NULL;
++    const char *description = NULL;
+     pa_assert(stream);
+     pa_assert(call_data);
+@@ -407,9 +329,7 @@ static pa_hook_result_t sink_input_or_source_output_proplist_changed_cb(void *ho
+             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;
++            proplist = stream->sink_input->proplist;
+             break;
+         case STREAM_TYPE_SOURCE_OUTPUT:
+@@ -418,187 +338,290 @@ static pa_hook_result_t sink_input_or_source_output_proplist_changed_cb(void *ho
+             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;
++            proplist = stream->source_output->proplist;
+             break;
+     }
+-    pas_stream_description_changed(stream->stream, new_stream_description);
+-
+-    new_control_description = get_stream_volume_and_mute_control_description_malloc(stream);
++    description = pa_proplist_gets(proplist, PA_PROP_MEDIA_NAME);
++    if (!description)
++        description = stream->stream->name;
+-    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);
++    pas_stream_set_description(stream->stream, 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;
++static int stream_new(pa_stream_creator *creator, enum stream_type type, void *new_data, void *core_stream,
++                      struct stream **_r) {
++    struct stream *stream = NULL;
++    pa_proplist *proplist = NULL;
++    pa_channel_map *channel_map = NULL;
++    bool volume_available = false;
++    pa_bvolume volume;
++    pa_bvolume relative_volume;
++    bool mute = false;
++    const char *stream_name = NULL;
+     const char *description = NULL;
++    const char *volume_control_name = NULL;
++    const char *relative_volume_control_name = NULL;
++    const char *mute_control_name = NULL;
+     pa_direction_t direction = PA_DIRECTION_OUTPUT;
++    int r;
++    const char *prop_key;
++    void *state = NULL;
+     pa_assert(creator);
+     pa_assert(core_stream);
++    pa_assert(_r);
++
++    pa_bvolume_init_invalid(&volume);
++    pa_bvolume_init_invalid(&relative_volume);
+     stream = pa_xnew0(struct stream, 1);
++    stream->core = creator->volume_api->core;
+     stream->creator = creator;
+     stream->type = type;
+     switch (type) {
+         case STREAM_TYPE_SINK_INPUT:
++            stream->sink_input_new_data = new_data;
+             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;
++            if (new_data) {
++                stream->client = stream->sink_input_new_data->client;
++                proplist = stream->sink_input_new_data->proplist;
++                channel_map = &stream->sink_input_new_data->channel_map;
++                volume_available = stream->sink_input_new_data->volume_writable;
++
++                if (volume_available) {
++                    if (!stream->sink_input_new_data->volume_is_set) {
++                        pa_cvolume cvolume;
++
++                        pa_cvolume_reset(&cvolume, channel_map->channels);
++                        pa_sink_input_new_data_set_volume(stream->sink_input_new_data, &cvolume, true);
++                    }
++
++                    pa_bvolume_from_cvolume(&volume, &stream->sink_input_new_data->volume, channel_map);
++                    pa_bvolume_from_cvolume(&relative_volume, &stream->sink_input_new_data->reference_ratio, channel_map);
++                }
++
++                if (!stream->sink_input_new_data->muted_is_set)
++                    pa_sink_input_new_data_set_muted(stream->sink_input_new_data, false);
++
++                mute = stream->sink_input_new_data->muted;
++            } else {
++                stream->client = stream->sink_input->client;
++                proplist = stream->sink_input->proplist;
++                channel_map = &stream->sink_input->channel_map;
++                pa_bvolume_from_cvolume(&volume, &stream->sink_input->volume, channel_map);
++                pa_bvolume_from_cvolume(&relative_volume, &stream->sink_input->reference_ratio, channel_map);
++                mute = stream->sink_input->muted;
++            }
++
++            stream_name = "sink-input-stream";
++            volume_control_name = "sink-input-volume-control";
++            relative_volume_control_name = "sink-input-relative-volume-control";
++            mute_control_name = "sink-input-mute-control";
+             direction = PA_DIRECTION_OUTPUT;
++
++            stream->proplist_changed_slot =
++                    pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_NORMAL,
++                                    sink_input_or_source_output_proplist_changed_cb, stream);
++            stream->volume_changed_slot =
++                    pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED], PA_HOOK_NORMAL,
++                                    sink_input_or_source_output_volume_changed_cb, stream);
++            stream->reference_ratio_changed_slot =
++                    pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SINK_INPUT_REFERENCE_RATIO_CHANGED], PA_HOOK_NORMAL,
++                                    sink_input_or_source_output_reference_ratio_changed_cb, stream);
++            stream->mute_changed_slot =
++                    pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], PA_HOOK_NORMAL,
++                                    sink_input_or_source_output_mute_changed_cb, stream);
+             break;
+         case STREAM_TYPE_SOURCE_OUTPUT:
++            stream->source_output_new_data = new_data;
+             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;
++            if (new_data) {
++                stream->client = stream->source_output_new_data->client;
++                proplist = stream->source_output_new_data->proplist;
++                channel_map = &stream->source_output_new_data->channel_map;
++                volume_available = stream->source_output_new_data->volume_writable;
++
++                if (volume_available) {
++                    if (!stream->source_output_new_data->volume_is_set) {
++                        pa_cvolume cvolume;
++
++                        pa_cvolume_reset(&cvolume, channel_map->channels);
++                        pa_source_output_new_data_set_volume(stream->source_output_new_data, &cvolume, true);
++                    }
++
++                    pa_bvolume_from_cvolume(&volume, &stream->source_output_new_data->volume, channel_map);
++                    pa_bvolume_from_cvolume(&relative_volume, &stream->source_output_new_data->reference_ratio, channel_map);
++                }
++
++                if (!stream->source_output_new_data->muted_is_set)
++                    pa_source_output_new_data_set_muted(stream->source_output_new_data, false);
++
++                mute = stream->source_output_new_data->muted;
++            } else {
++                stream->client = stream->source_output->client;
++                proplist = stream->source_output->proplist;
++                channel_map = &stream->source_output->channel_map;
++                pa_bvolume_from_cvolume(&volume, &stream->source_output->volume, channel_map);
++                pa_bvolume_from_cvolume(&relative_volume, &stream->source_output->reference_ratio, channel_map);
++                mute = stream->source_output->muted;
++            }
++
++            stream_name = "source-output-stream";
++            volume_control_name = "source-output-volume-control";
++            relative_volume_control_name = "source-output-relative-volume-control";
++            mute_control_name = "source-output-mute-control";
+             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,
++                    pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_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);
++            if (volume_available) {
++                stream->volume_changed_slot =
++                        pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED], PA_HOOK_NORMAL,
++                                        sink_input_or_source_output_volume_changed_cb, stream);
++                stream->reference_ratio_changed_slot =
++                        pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_REFERENCE_RATIO_CHANGED],
++                                        PA_HOOK_NORMAL, sink_input_or_source_output_reference_ratio_changed_cb, stream);
++            }
++
++            stream->mute_changed_slot =
++                    pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED], PA_HOOK_NORMAL,
++                                    sink_input_or_source_output_mute_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);
++    r = pas_stream_new(creator->volume_api, stream_name, &stream->stream);
++    if (r < 0)
++        goto fail;
+-    return stream;
+-}
++    description = pa_proplist_gets(proplist, PA_PROP_MEDIA_NAME);
++    if (!description)
++        description = stream->stream->name;
+-static void stream_put(struct stream *stream) {
+-    pa_proplist *proplist = NULL;
++    pas_stream_set_description(stream->stream, description);
+-    pa_assert(stream);
++    while ((prop_key = pa_proplist_iterate(proplist, &state)))
++        pas_stream_set_property(stream->stream, prop_key, pa_proplist_gets(proplist, prop_key));
+-    switch (stream->type) {
+-        case STREAM_TYPE_SINK_INPUT:
+-            proplist = stream->sink_input->proplist;
+-            break;
++    pas_stream_set_direction(stream->stream, direction);
++    stream->stream->userdata = stream;
+-        case STREAM_TYPE_SOURCE_OUTPUT:
+-            proplist = stream->source_output->proplist;
+-            break;
++    if (volume_available) {
++        r = pa_volume_control_new(stream->creator->volume_api, volume_control_name, false,
++                                  &stream->volume_control);
++        if (r >= 0) {
++            pa_volume_control_set_description(stream->volume_control, _("Volume"));
++            pa_volume_control_set_channel_map(stream->volume_control, channel_map);
++            pa_volume_control_set_volume(stream->volume_control, &volume, true, true);
++            pa_volume_control_set_convertible_to_dB(stream->volume_control, true);
++            stream->volume_control->set_volume = volume_control_set_volume_cb;
++            stream->volume_control->userdata = stream;
++
++            pas_stream_set_volume_control(stream->stream, stream->volume_control);
++        }
++
++        r = pa_volume_control_new(stream->creator->volume_api, relative_volume_control_name, false,
++                                  &stream->relative_volume_control);
++        if (r >= 0) {
++            pa_volume_control_set_description(stream->relative_volume_control, _("Relative volume"));
++            pa_volume_control_set_channel_map(stream->relative_volume_control, channel_map);
++            pa_volume_control_set_volume(stream->relative_volume_control, &relative_volume, true, true);
++            pa_volume_control_set_convertible_to_dB(stream->relative_volume_control, true);
++            pa_volume_control_set_purpose(stream->relative_volume_control, PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME,
++                                          stream->stream);
++            stream->relative_volume_control->set_volume = relative_volume_control_set_volume_cb;
++            stream->relative_volume_control->userdata = stream;
++
++            pas_stream_set_relative_volume_control(stream->stream, stream->relative_volume_control);
++        }
+     }
+-    pas_stream_put(stream->stream, proplist);
+-}
++    r = pa_mute_control_new(stream->creator->volume_api, mute_control_name, false, &stream->mute_control);
++    if (r >= 0) {
++        pa_mute_control_set_description(stream->mute_control, _("Mute"));
++        pa_mute_control_set_mute(stream->mute_control, mute);
++        pa_mute_control_set_purpose(stream->mute_control, PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE, stream->stream);
++        stream->mute_control->set_mute = mute_control_set_mute_cb;
++        stream->mute_control->userdata = stream;
+-static void stream_unlink(struct stream *stream) {
+-    pa_assert(stream);
++        pas_stream_set_mute_control(stream->stream, stream->mute_control);
++    }
+-    if (stream->unlinked)
+-        return;
++    pas_stream_put(stream->stream);
+-    stream->unlinked = true;
++    if (stream->volume_control)
++        pa_volume_control_put(stream->volume_control);
+-    if (stream->stream)
+-        pas_stream_unlink(stream->stream);
++    if (stream->relative_volume_control)
++        pa_volume_control_put(stream->relative_volume_control);
++
++    if (stream->mute_control)
++        pa_mute_control_put(stream->mute_control);
++
++    *_r = stream;
++    return 0;
++
++fail:
++    if (stream)
++        stream_free(stream);
++
++    return r;
+ }
+ static void stream_free(struct stream *stream) {
+     pa_assert(stream);
+-    if (!stream->unlinked)
+-        stream_unlink(stream);
++    if (stream->mute_changed_slot)
++        pa_hook_slot_free(stream->mute_changed_slot);
++
++    if (stream->reference_ratio_changed_slot)
++        pa_hook_slot_free(stream->reference_ratio_changed_slot);
+-    if (stream->client_proplist_changed_slot)
+-        pa_hook_slot_free(stream->client_proplist_changed_slot);
++    if (stream->volume_changed_slot)
++        pa_hook_slot_free(stream->volume_changed_slot);
+     if (stream->proplist_changed_slot)
+         pa_hook_slot_free(stream->proplist_changed_slot);
++    if (stream->mute_control)
++        pa_mute_control_free(stream->mute_control);
++
++    if (stream->relative_volume_control)
++        pa_volume_control_free(stream->relative_volume_control);
++
++    if (stream->volume_control)
++        pa_volume_control_free(stream->volume_control);
++
+     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) {
++static pa_hook_result_t sink_input_fixate_cb(void *hook_data, void *call_data, void *userdata) {
++    pa_stream_creator *creator = userdata;
++    pa_sink_input_new_data *data = call_data;
++    int r;
+     struct stream *stream;
+     pa_assert(creator);
+-    pa_assert(core_stream);
++    pa_assert(data);
+-    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);
++    r = stream_new(creator, STREAM_TYPE_SINK_INPUT, data, data->sink_input, &stream);
++    if (r < 0)
++        return PA_HOOK_OK;
+-    create_stream(creator, STREAM_TYPE_SINK_INPUT, input);
++    pa_hashmap_put(creator->streams, stream->sink_input, stream);
+     return PA_HOOK_OK;
+ }
+@@ -615,14 +638,20 @@ static pa_hook_result_t sink_input_unlink_cb(void *hook_data, void *call_data, v
+     return PA_HOOK_OK;
+ }
+-static pa_hook_result_t source_output_put_cb(void *hook_data, void *call_data, void *userdata) {
++static pa_hook_result_t source_output_fixate_cb(void *hook_data, void *call_data, void *userdata) {
+     pa_stream_creator *creator = userdata;
+-    pa_source_output *output = call_data;
++    pa_source_output_new_data *data = call_data;
++    int r;
++    struct stream *stream;
+     pa_assert(creator);
+-    pa_assert(output);
++    pa_assert(data);
++
++    r = stream_new(creator, STREAM_TYPE_SOURCE_OUTPUT, data, data->source_output, &stream);
++    if (r < 0)
++        return PA_HOOK_OK;
+-    create_stream(creator, STREAM_TYPE_SOURCE_OUTPUT, output);
++    pa_hashmap_put(creator->streams, stream->source_output, stream);
+     return PA_HOOK_OK;
+ }
+@@ -644,26 +673,34 @@ pa_stream_creator *pa_stream_creator_new(pa_volume_api *api) {
+     uint32_t idx;
+     pa_sink_input *input;
+     pa_source_output *output;
++    int r;
++    struct stream *stream;
+     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_fixate_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_NORMAL,
++                                                      sink_input_fixate_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_fixate_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], PA_HOOK_NORMAL,
++                                                         source_output_fixate_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(input, api->core->sink_inputs, idx) {
++        r = stream_new(creator, STREAM_TYPE_SINK_INPUT, NULL, input, &stream);
++        if (r >= 0)
++            pa_hashmap_put(creator->streams, stream->sink_input, stream);
++    }
+-    PA_IDXSET_FOREACH(output, api->core->source_outputs, idx)
+-        create_stream(creator, STREAM_TYPE_SOURCE_OUTPUT, output);
++    PA_IDXSET_FOREACH(output, api->core->source_outputs, idx) {
++        r = stream_new(creator, STREAM_TYPE_SOURCE_OUTPUT, NULL, output, &stream);
++        if (r >= 0)
++            pa_hashmap_put(creator->streams, stream->source_output, stream);
++    }
+     return creator;
+ }
+@@ -677,14 +714,14 @@ void pa_stream_creator_free(pa_stream_creator *creator) {
+     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->source_output_fixate_slot)
++        pa_hook_slot_free(creator->source_output_fixate_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->sink_input_fixate_slot)
++        pa_hook_slot_free(creator->sink_input_fixate_slot);
+     if (creator->streams)
+         pa_hashmap_free(creator->streams);
+diff --git a/src/modules/volume-api/volume-api.c b/src/modules/volume-api/volume-api.c
+index 9abea7e..4a8a2e6 100644
+--- a/src/modules/volume-api/volume-api.c
++++ b/src/modules/volume-api/volume-api.c
+@@ -26,16 +26,20 @@
+ #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/inidb.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/namereg.h>
+ #include <pulsecore/shared.h>
++#define CONTROL_DB_TABLE_NAME_VOLUME_CONTROL "VolumeControl"
++#define CONTROL_DB_TABLE_NAME_MUTE_CONTROL "MuteControl"
++
+ static pa_volume_api *volume_api_new(pa_core *core);
+ static void volume_api_free(pa_volume_api *api);
+@@ -76,44 +80,209 @@ 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) {
+-    pa_assert(api);
+-    pa_assert(type);
++static int control_db_get_volume_control_cb(pa_inidb *db, const char *name, void **_r) {
++    pa_volume_api *api;
++    pa_volume_control *control;
++
++    pa_assert(db);
++    pa_assert(name);
++    pa_assert(_r);
++
++    api = pa_inidb_get_userdata(db);
+-    pa_assert_se(pa_hashmap_put(api->binding_target_types, type->name, type) >= 0);
++    control = pa_hashmap_get(api->volume_controls_from_db, name);
++    if (!control) {
++        int r;
+-    pa_log_debug("Added binding target type %s.", type->name);
++        r = pa_volume_control_new(api, name, true, &control);
++        if (r < 0)
++            return r;
+-    pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_ADDED], type);
++        pa_hashmap_put(api->volume_controls_from_db, (void *) control->name, control);
++    }
++
++    *_r = control;
++    return 0;
+ }
+-void pa_volume_api_remove_binding_target_type(pa_volume_api *api, pa_binding_target_type *type) {
+-    pa_assert(api);
+-    pa_assert(type);
++static int control_db_parse_volume_control_description_cb(pa_inidb *db, const char *value, void *object) {
++    pa_volume_control *control = object;
++
++    pa_assert(db);
++    pa_assert(value);
++    pa_assert(control);
+-    pa_log_debug("Removing binding target type %s.", type->name);
++    pa_volume_control_set_description(control, value);
+-    pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_REMOVED], type);
++    return 0;
++}
++
++static int control_db_parse_volume_control_volume_cb(pa_inidb *db, const char *value, void *object) {
++    pa_volume_control *control = object;
++    int r;
++    pa_bvolume bvolume;
++
++    pa_assert(db);
++    pa_assert(value);
++    pa_assert(control);
++
++    r = pa_atou(value, &bvolume.volume);
++    if (r < 0)
++        return -PA_ERR_INVALID;
++
++    if (!PA_VOLUME_IS_VALID(bvolume.volume))
++        return -PA_ERR_INVALID;
+-    pa_assert_se(pa_hashmap_remove(api->binding_target_types, type->name));
++    pa_volume_control_set_volume(control, &bvolume, true, false);
++
++    return 0;
+ }
+-static void create_builtin_binding_target_types(pa_volume_api *api) {
+-    pa_binding_target_type *type;
++static int control_db_parse_volume_control_balance_cb(pa_inidb *db, const char *value, void *object) {
++    pa_volume_control *control = object;
++    int r;
++    pa_bvolume bvolume;
+-    pa_assert(api);
++    pa_assert(db);
++    pa_assert(value);
++    pa_assert(control);
++
++    r = pa_bvolume_parse_balance(value, &bvolume);
++    if (r < 0)
++        return -PA_ERR_INVALID;
++
++    pa_volume_control_set_channel_map(control, &bvolume.channel_map);
++    pa_volume_control_set_volume(control, &bvolume, false, true);
++
++    return 0;
++}
++
++static int control_db_parse_volume_control_convertible_to_dB_cb(pa_inidb *db, const char *value, void *object) {
++    pa_volume_control *control = object;
++    int r;
++
++    pa_assert(db);
++    pa_assert(value);
++    pa_assert(control);
++
++    r = pa_parse_boolean(value);
++    if (r < 0)
++        return -PA_ERR_INVALID;
++
++    pa_volume_control_set_convertible_to_dB(control, r);
++
++    return 0;
++}
++
++static int control_db_get_mute_control_cb(pa_inidb *db, const char *name, void **_r) {
++    pa_volume_api *api;
++    pa_mute_control *control;
++
++    pa_assert(db);
++    pa_assert(name);
++    pa_assert(_r);
++
++    api = pa_inidb_get_userdata(db);
++
++    control = pa_hashmap_get(api->mute_controls_from_db, name);
++    if (!control) {
++        int r;
++
++        r = pa_mute_control_new(api, name, true, &control);
++        if (r < 0)
++            return r;
++
++        pa_hashmap_put(api->mute_controls_from_db, (void *) control->name, control);
++    }
++
++    *_r = control;
++    return 0;
++}
++
++static int control_db_parse_mute_control_description_cb(pa_inidb *db, const char *value, void *object) {
++    pa_mute_control *control = object;
+-    type = pa_audio_group_create_binding_target_type(api);
+-    pa_volume_api_add_binding_target_type(api, type);
++    pa_assert(db);
++    pa_assert(value);
++    pa_assert(control);
++
++    pa_mute_control_set_description(control, value);
++
++    return 0;
+ }
+-static void delete_builtin_binding_target_types(pa_volume_api *api) {
+-    pa_binding_target_type *type;
++static int control_db_parse_mute_control_mute_cb(pa_inidb *db, const char *value, void *object) {
++    pa_mute_control *control = object;
++    int mute;
++
++    pa_assert(db);
++    pa_assert(value);
++    pa_assert(control);
++
++    mute = pa_parse_boolean(value);
++    if (mute < 0)
++        return -PA_ERR_INVALID;
++
++    pa_mute_control_set_mute(control, mute);
++
++    return 0;
++}
++
++static void create_control_db(pa_volume_api *api) {
++    pa_volume_control *volume_control;
++    pa_mute_control *mute_control;
++
++    pa_assert(api);
++    pa_assert(!api->control_db.db);
++
++    api->control_db.db = pa_inidb_new(api->core, "controls", api);
++
++    api->control_db.volume_controls = pa_inidb_add_table(api->control_db.db, CONTROL_DB_TABLE_NAME_VOLUME_CONTROL,
++                                                         control_db_get_volume_control_cb);
++    pa_inidb_table_add_column(api->control_db.volume_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_DESCRIPTION,
++                              control_db_parse_volume_control_description_cb);
++    pa_inidb_table_add_column(api->control_db.volume_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_VOLUME,
++                              control_db_parse_volume_control_volume_cb);
++    pa_inidb_table_add_column(api->control_db.volume_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_BALANCE,
++                              control_db_parse_volume_control_balance_cb);
++    pa_inidb_table_add_column(api->control_db.volume_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_CONVERTIBLE_TO_DB,
++                              control_db_parse_volume_control_convertible_to_dB_cb);
++
++    api->control_db.mute_controls = pa_inidb_add_table(api->control_db.db, CONTROL_DB_TABLE_NAME_MUTE_CONTROL,
++                                                       control_db_get_mute_control_cb);
++    pa_inidb_table_add_column(api->control_db.mute_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_DESCRIPTION,
++                              control_db_parse_mute_control_description_cb);
++    pa_inidb_table_add_column(api->control_db.mute_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_MUTE,
++                              control_db_parse_mute_control_mute_cb);
++
++    api->volume_controls_from_db = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
++    api->mute_controls_from_db = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
++
++    pa_inidb_load(api->control_db.db);
++
++    while ((volume_control = pa_hashmap_steal_first(api->volume_controls_from_db)))
++        pa_volume_control_put(volume_control);
++    pa_hashmap_free(api->volume_controls_from_db);
++    api->volume_controls_from_db = NULL;
++
++    while ((mute_control = pa_hashmap_steal_first(api->mute_controls_from_db)))
++        pa_mute_control_put(mute_control);
++
++    pa_hashmap_free(api->mute_controls_from_db);
++    api->mute_controls_from_db = NULL;
++}
++
++static void delete_control_db(pa_volume_api *api) {
+     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);
++    if (!api->control_db.db)
++        return;
++
++    pa_inidb_free(api->control_db.db);
++    api->control_db.mute_controls = NULL;
++    api->control_db.volume_controls = NULL;
++    api->control_db.db = NULL;
+ }
+ static void create_objects_defer_event_cb(pa_mainloop_api *mainloop_api, pa_defer_event *event, void *userdata) {
+@@ -138,7 +307,6 @@ static pa_volume_api *volume_api_new(pa_core *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);
+@@ -149,7 +317,7 @@ static pa_volume_api *volume_api_new(pa_core *core) {
+     for (i = 0; i < PA_VOLUME_API_HOOK_MAX; i++)
+         pa_hook_init(&api->hooks[i], api);
+-    create_builtin_binding_target_types(api);
++    create_control_db(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
+@@ -170,6 +338,9 @@ static void volume_api_free(pa_volume_api *api) {
+     pa_log_debug("Freeing the pa_volume_api object.");
++    pa_assert(!api->mute_controls_from_db);
++    pa_assert(!api->volume_controls_from_db);
++
+     if (api->stream_creator)
+         pa_stream_creator_free(api->stream_creator);
+@@ -179,8 +350,7 @@ static void volume_api_free(pa_volume_api *api) {
+     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);
++    delete_control_db(api);
+     for (i = 0; i < PA_VOLUME_API_HOOK_MAX; i++)
+         pa_hook_done(&api->hooks[i]);
+@@ -201,12 +371,24 @@ static void volume_api_free(pa_volume_api *api) {
+     }
+     if (api->mute_controls) {
+-        pa_assert(pa_hashmap_isempty(api->mute_controls));
++        pa_mute_control *control;
++
++        while ((control = pa_hashmap_first(api->mute_controls))) {
++            pa_assert(!control->present);
++            pa_mute_control_free(control);
++        }
++
+         pa_hashmap_free(api->mute_controls);
+     }
+     if (api->volume_controls) {
+-        pa_assert(pa_hashmap_isempty(api->volume_controls));
++        pa_volume_control *control;
++
++        while ((control = pa_hashmap_first(api->volume_controls))) {
++            pa_assert(!control->present);
++            pa_volume_control_free(control);
++        }
++
+         pa_hashmap_free(api->volume_controls);
+     }
+@@ -215,11 +397,6 @@ static void volume_api_free(pa_volume_api *api) {
+         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);
+ }
+@@ -231,19 +408,24 @@ int pa_volume_api_register_name(pa_volume_api *api, const char *requested_name,
+     pa_assert(requested_name);
+     pa_assert(registered_name);
++    if (!pa_namereg_is_valid_name(requested_name)) {
++        pa_log("Invalid name: \"%s\"", requested_name);
++        return -PA_ERR_INVALID;
++    }
++
+     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_xfree(n);
+             pa_log("Name %s already registered.", requested_name);
+             return -PA_ERR_EXIST;
+         }
+         do {
++            pa_xfree(n);
+             i++;
+             n = pa_sprintf_malloc("%s.%u", requested_name, i);
+         } while (pa_hashmap_put(api->names, n, n) < 0);
+@@ -271,38 +453,6 @@ uint32_t pa_volume_api_allocate_volume_control_index(pa_volume_api *api) {
+     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);
+@@ -318,10 +468,10 @@ int pa_volume_api_remove_volume_control(pa_volume_api *api, pa_volume_control *c
+         return -1;
+     if (control == api->main_output_volume_control)
+-        set_main_output_volume_control_internal(api, NULL);
++        pa_volume_api_set_main_output_volume_control(api, NULL);
+     if (control == api->main_input_volume_control)
+-        set_main_input_volume_control_internal(api, NULL);
++        pa_volume_api_set_main_input_volume_control(api, NULL);
+     return 0;
+ }
+@@ -350,38 +500,6 @@ uint32_t pa_volume_api_allocate_mute_control_index(pa_volume_api *api) {
+     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);
+@@ -397,10 +515,10 @@ int pa_volume_api_remove_mute_control(pa_volume_api *api, pa_mute_control *contr
+         return -1;
+     if (control == api->main_output_mute_control)
+-        set_main_output_mute_control_internal(api, NULL);
++        pa_volume_api_set_main_output_mute_control(api, NULL);
+     if (control == api->main_input_mute_control)
+-        set_main_input_mute_control_internal(api, NULL);
++        pa_volume_api_set_main_input_mute_control(api, NULL);
+     return 0;
+ }
+@@ -543,105 +661,73 @@ pa_audio_group *pa_volume_api_get_audio_group_by_index(pa_volume_api *api, uint3
+ }
+ void pa_volume_api_set_main_output_volume_control(pa_volume_api *api, pa_volume_control *control) {
++    pa_volume_control *old_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;
+-    }
++    old_control = api->main_output_volume_control;
+-    set_main_output_volume_control_internal(api, control);
+-}
++    if (control == old_control)
++        return;
+-void pa_volume_api_set_main_input_volume_control(pa_volume_api *api, pa_volume_control *control) {
+-    pa_assert(api);
++    api->main_output_volume_control = control;
+-    if (api->main_input_volume_control_binding) {
+-        pa_binding_free(api->main_input_volume_control_binding);
+-        api->main_input_volume_control_binding = NULL;
+-    }
++    pa_log_debug("Main output volume control changed from %s to %s.", old_control ? old_control->name : "(unset)",
++                 control ? control->name : "(unset)");
+-    set_main_input_volume_control_internal(api, control);
++    pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED], api);
+ }
+-void pa_volume_api_set_main_output_mute_control(pa_volume_api *api, pa_mute_control *control) {
++void pa_volume_api_set_main_input_volume_control(pa_volume_api *api, pa_volume_control *control) {
++    pa_volume_control *old_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;
+-    }
++    old_control = api->main_input_volume_control;
+-    set_main_output_mute_control_internal(api, control);
+-}
++    if (control == old_control)
++        return;
+-void pa_volume_api_set_main_input_mute_control(pa_volume_api *api, pa_mute_control *control) {
+-    pa_assert(api);
++    api->main_input_volume_control = control;
+-    if (api->main_input_mute_control_binding) {
+-        pa_binding_free(api->main_input_mute_control_binding);
+-        api->main_input_mute_control_binding = NULL;
+-    }
++    pa_log_debug("Main input volume control changed from %s to %s.", old_control ? old_control->name : "(unset)",
++                 control ? control->name : "(unset)");
+-    set_main_input_mute_control_internal(api, control);
++    pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_INPUT_VOLUME_CONTROL_CHANGED], api);
+ }
+-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,
+-    };
++void pa_volume_api_set_main_output_mute_control(pa_volume_api *api, pa_mute_control *control) {
++    pa_mute_control *old_control;
+     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);
+-}
++    old_control = api->main_output_mute_control;
+-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,
+-    };
++    if (control == old_control)
++        return;
+-    pa_assert(api);
+-    pa_assert(target_info);
++    api->main_output_mute_control = control;
+-    if (api->main_input_volume_control_binding)
+-        pa_binding_free(api->main_input_volume_control_binding);
++    pa_log_debug("Main output mute control changed from %s to %s.", old_control ? old_control->name : "(unset)",
++                 control ? control->name : "(unset)");
+-    api->main_input_volume_control_binding = pa_binding_new(api, &owner_info, target_info);
++    pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_OUTPUT_MUTE_CONTROL_CHANGED], api);
+ }
+-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,
+-    };
++void pa_volume_api_set_main_input_mute_control(pa_volume_api *api, pa_mute_control *control) {
++    pa_mute_control *old_control;
+     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);
+-}
++    old_control = api->main_input_mute_control;
+-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,
+-    };
++    if (control == old_control)
++        return;
+-    pa_assert(api);
+-    pa_assert(target_info);
++    api->main_input_mute_control = control;
+-    if (api->main_input_mute_control_binding)
+-        pa_binding_free(api->main_input_mute_control_binding);
++    pa_log_debug("Main input mute control changed from %s to %s.", old_control ? old_control->name : "(unset)",
++                 control ? control->name : "(unset)");
+-    api->main_input_mute_control_binding = pa_binding_new(api, &owner_info, target_info);
++    pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_INPUT_MUTE_CONTROL_CHANGED], api);
+ }
+diff --git a/src/modules/volume-api/volume-api.h b/src/modules/volume-api/volume-api.h
+index 73a1410..f99182a 100644
+--- a/src/modules/volume-api/volume-api.h
++++ b/src/modules/volume-api/volume-api.h
+@@ -24,27 +24,56 @@
+ #include <pulsecore/core.h>
++#define PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_DESCRIPTION "description"
++#define PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_VOLUME "volume"
++#define PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_BALANCE "balance"
++#define PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_CONVERTIBLE_TO_DB "convertible-to-dB"
++#define PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_MUTE "mute"
++
+ 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_inidb pa_inidb;
++typedef struct pa_inidb_table pa_inidb_table;
+ 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,
++    /* This is fired after the volume control implementation has done its part
++     * of the volume control initialization, but before policy modules have
++     * done their part of the initialization. Hook users are expected to not
++     * modify the volume control state in this hook. */
++    PA_VOLUME_API_HOOK_VOLUME_CONTROL_IMPLEMENTATION_INITIALIZED,
++
++    /* Policy modules can use this hook to initialize the volume control
++     * volume. This is fired before PUT. If a policy module sets the volume, it
++     * should return PA_HOOK_STOP to prevent lower-priority policy modules from
++     * modifying the volume. */
++    PA_VOLUME_API_HOOK_VOLUME_CONTROL_SET_INITIAL_VOLUME,
++
+     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_VOLUME_CONTROL_CONVERTIBLE_TO_DB_CHANGED,
++
++    /* This is fired after the mute control implementation has done its part of
++     * the mute control initialization, but before policy modules have done
++     * their part of the initialization. Hook users are expected to not modify
++     * the mute control state in this hook. */
++    PA_VOLUME_API_HOOK_MUTE_CONTROL_IMPLEMENTATION_INITIALIZED,
++
++    /* Policy modules can use this hook to initialize the mute control mute.
++     * This is fired before PUT. If a policy module sets the mute, it should
++     * return PA_HOOK_STOP to prevent lower-priority policy modules from
++     * modifying the mute. */
++    PA_VOLUME_API_HOOK_MUTE_CONTROL_SET_INITIAL_MUTE,
++
+     PA_VOLUME_API_HOOK_MUTE_CONTROL_PUT,
+     PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK,
+     PA_VOLUME_API_HOOK_MUTE_CONTROL_DESCRIPTION_CHANGED,
+@@ -54,26 +83,16 @@ enum {
+     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_PROPLIST_CHANGED,
+     PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED,
++    PA_VOLUME_API_HOOK_STREAM_RELATIVE_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_DESCRIPTION_CHANGED,
+     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,
+@@ -86,7 +105,6 @@ enum {
+ 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 */
+@@ -103,27 +121,28 @@ struct pa_volume_api {
+     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];
++
++    struct {
++        pa_inidb *db;
++        pa_inidb_table *volume_controls;
++        pa_inidb_table *mute_controls;
++    } control_db;
++
+     pa_defer_event *create_objects_defer_event;
+     pa_device_creator *device_creator;
+     pa_stream_creator *stream_creator;
++
++    pa_hashmap *volume_controls_from_db; /* control name -> pa_volume_control, only used during initialization. */
++    pa_hashmap *mute_controls_from_db; /* control name -> pa_mute_control, only used during initialization. */
+ };
+ 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);
+@@ -155,9 +174,5 @@ void pa_volume_api_set_main_output_volume_control(pa_volume_api *api, pa_volume_
+ 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
+index c7f5dbb..bf4db71 100644
+--- a/src/modules/volume-api/volume-control.c
++++ b/src/modules/volume-api/volume-control.c
+@@ -27,88 +27,96 @@
+ #include <modules/volume-api/audio-group.h>
+ #include <modules/volume-api/device.h>
++#include <modules/volume-api/inidb.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;
++int pa_volume_control_new(pa_volume_api *api, const char *name, bool persistent, pa_volume_control **_r) {
++    pa_volume_control *control = NULL;
++    int r;
+     pa_assert(api);
+     pa_assert(name);
+-    pa_assert(description);
++    pa_assert(_r);
+     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);
++
++    r = pa_volume_api_register_name(api, name, persistent, &control->name);
++    if (r < 0)
++        goto fail;
++
++    control->description = pa_xstrdup(control->name);
+     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;
++    pa_bvolume_init_mono(&control->volume, PA_VOLUME_NORM);
++    control->present = !persistent;
++    control->persistent = persistent;
++    control->purpose = PA_VOLUME_CONTROL_PURPOSE_OTHER;
+     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;
++    if (persistent) {
++        pa_inidb_row *row;
++
++        row = pa_inidb_table_add_row(api->control_db.volume_controls, control->name);
++        control->db_cells.description = pa_inidb_row_get_cell(row, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_DESCRIPTION);
++        control->db_cells.volume = pa_inidb_row_get_cell(row, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_VOLUME);
++        control->db_cells.balance = pa_inidb_row_get_cell(row, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_BALANCE);
++        control->db_cells.convertible_to_dB = pa_inidb_row_get_cell(row,
++                                                                    PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_CONVERTIBLE_TO_DB);
++    }
++
++    *_r = control;
++    return 0;
++
++fail:
++    if (control)
++        pa_volume_control_free(control);
++
++    return r;
+ }
+-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_put(pa_volume_control *control) {
+     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);
+-    }
++    pa_assert(control->set_volume || !control->present);
+-    if (set_initial_volume_cb)
+-        set_initial_volume_cb(control);
++    pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_IMPLEMENTATION_INITIALIZED], control);
++    pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_SET_INITIAL_VOLUME], control);
+-    pa_volume_api_add_volume_control(control->volume_api, control);
++    if (control->set_volume) {
++        control->set_volume_in_progress = true;
++        control->set_volume(control, &control->volume, &control->volume, true, true);
++        control->set_volume_in_progress = false;
++    }
++    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("    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("    Present: %s", pa_yes_no(control->present));
++    pa_log_debug("    Persistent: %s", pa_yes_no(control->persistent));
+     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);
+@@ -122,15 +130,9 @@ void pa_volume_control_unlink(pa_volume_control *control) {
+     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);
++        pa_volume_api_remove_volume_control(control->volume_api, control);
+-    while ((stream = pa_hashmap_first(control->streams)))
+-        pas_stream_set_volume_control(stream, NULL);
++    pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK], control);
+     while ((device = pa_hashmap_first(control->default_for_devices)))
+         pa_device_set_default_volume_control(device, NULL);
+@@ -153,19 +155,10 @@ void pa_volume_control_unlink(pa_volume_control *control) {
+ void pa_volume_control_free(pa_volume_control *control) {
+     pa_assert(control);
+-    if (!control->unlinked)
++    /* unlink() expects name to be set. */
++    if (!control->unlinked && control->name)
+         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);
+@@ -187,17 +180,91 @@ void pa_volume_control_free(pa_volume_control *control) {
+     pa_xfree(control);
+ }
+-void pa_volume_control_set_owner_audio_group(pa_volume_control *control, pa_audio_group *group) {
++void pa_volume_control_set_purpose(pa_volume_control *control, pa_volume_control_purpose_t purpose, void *owner) {
++    pa_assert(control);
++    pa_assert(!control->linked);
++
++    control->purpose = purpose;
++    control->owner = owner;
++}
++
++int pa_volume_control_acquire_for_audio_group(pa_volume_control *control, pa_audio_group *group,
++                                              pa_volume_control_set_volume_cb_t set_volume_cb, void *userdata) {
+     pa_assert(control);
+     pa_assert(group);
++    pa_assert(set_volume_cb);
++
++    if (control->present) {
++        pa_log("Can't acquire volume control %s, it's already present.", control->name);
++        return -PA_ERR_BUSY;
++    }
++
++    control->set_volume = set_volume_cb;
++    control->userdata = userdata;
++
++    control->set_volume_in_progress = true;
++    control->set_volume(control, &control->volume, &control->volume, true, true);
++    control->set_volume_in_progress = false;
++
++    control->present = true;
++
++    if (!control->linked || control->unlinked)
++        return 0;
++
++    pa_log_debug("Volume control %s became present.", control->name);
++
++    return 0;
++}
++
++void pa_volume_control_release(pa_volume_control *control) {
++    pa_assert(control);
++
++    if (!control->present)
++        return;
++
++    control->present = false;
++
++    control->userdata = NULL;
++    control->set_volume = NULL;
++
++    if (!control->linked || control->unlinked)
++        return;
++
++    pa_log_debug("Volume control %s became not present.", control->name);
++}
++
++void pa_volume_control_set_description(pa_volume_control *control, const char *description) {
++    char *old_description;
++
++    pa_assert(control);
++    pa_assert(description);
+-    control->owner_audio_group = group;
++    old_description = control->description;
++
++    if (pa_streq(description, old_description))
++        return;
++
++    control->description = pa_xstrdup(description);
++
++    if (control->persistent)
++        pa_inidb_cell_set_value(control->db_cells.description, description);
++
++    if (!control->linked || control->unlinked) {
++        pa_xfree(old_description);
++        return;
++    }
++
++    pa_log_debug("The description of volume control %s changed from \"%s\" to \"%s\".", control->name, old_description,
++                 description);
++    pa_xfree(old_description);
++    pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_DESCRIPTION_CHANGED], control);
+ }
+ 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;
++    char *str;
+     pa_assert(control);
+     pa_assert(volume);
+@@ -209,12 +276,26 @@ static void set_volume_internal(pa_volume_control *control, const pa_bvolume *vo
+     if (!volume_changed && !balance_changed)
+         return;
+-    if (volume_changed)
++    if (volume_changed) {
+         control->volume.volume = volume->volume;
+-    if (balance_changed)
++        if (control->persistent) {
++            str = pa_sprintf_malloc("%u", control->volume.volume);
++            pa_inidb_cell_set_value(control->db_cells.volume, str);
++            pa_xfree(str);
++        }
++    }
++
++    if (balance_changed) {
+         pa_bvolume_copy_balance(&control->volume, volume);
++        if (control->persistent) {
++            pa_assert_se(pa_bvolume_balance_to_string(&control->volume, &str) >= 0);
++            pa_inidb_cell_set_value(control->db_cells.balance, str);
++            pa_xfree(str);
++        }
++    }
++
+     if (!control->linked || control->unlinked)
+         return;
+@@ -248,62 +329,69 @@ int pa_volume_control_set_volume(pa_volume_control *control, const pa_bvolume *v
+     pa_assert(control);
+     pa_assert(volume);
+-    volume_local = *volume;
++    if (control->set_volume_in_progress)
++        return 0;
+-    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;
+-    }
++    volume_local = *volume;
+-    if (set_balance
+-            && !control->channel_map_is_writable
+-            && !pa_channel_map_equal(&volume_local.channel_map, &control->volume.channel_map))
++    if (set_balance && !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 (control->linked && control->present) {
++        control->set_volume_in_progress = true;
++        r = control->set_volume(control, volume, &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);
++        if (r < 0) {
++            pa_log("Setting the volume of volume control %s failed.", control->name);
++            return r;
++        }
++    }
+-    return r;
++    set_volume_internal(control, &volume_local, set_volume, set_balance);
++
++    return 0;
+ }
+-void pa_volume_control_description_changed(pa_volume_control *control, const char *new_description) {
+-    char *old_description;
++void pa_volume_control_set_channel_map(pa_volume_control *control, const pa_channel_map *map) {
++    pa_bvolume bvolume;
+     pa_assert(control);
+-    pa_assert(new_description);
++    pa_assert(map);
+-    old_description = control->description;
+-
+-    if (pa_streq(new_description, old_description))
++    if (pa_channel_map_equal(map, &control->volume.channel_map))
+         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);
++    pa_bvolume_copy_balance(&bvolume, &control->volume);
++    pa_bvolume_remap(&bvolume, map);
++
++    set_volume_internal(control, &bvolume, false, true);
+ }
+-void pa_volume_control_volume_changed(pa_volume_control *control, const pa_bvolume *new_volume, bool volume_changed,
+-                                      bool balance_changed) {
++void pa_volume_control_set_convertible_to_dB(pa_volume_control *control, bool convertible) {
++    bool old_convertible;
++
+     pa_assert(control);
+-    pa_assert(new_volume);
+-    if (!control->linked)
++    old_convertible = control->convertible_to_dB;
++
++    if (convertible == old_convertible)
+         return;
+-    if (control->set_volume_in_progress)
++    control->convertible_to_dB = convertible;
++
++    if (control->persistent)
++        pa_inidb_cell_set_value(control->db_cells.convertible_to_dB, pa_boolean_to_string(convertible));
++
++    if (!control->linked || control->unlinked)
+         return;
+-    set_volume_internal(control, new_volume, volume_changed, balance_changed);
++    pa_log_debug("The volume of volume control %s became %sconvertible to dB.", control->name, convertible ? "" : "not ");
++
++    pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_CONVERTIBLE_TO_DB_CHANGED], control);
+ }
+ void pa_volume_control_add_device(pa_volume_control *control, pa_device *device) {
+@@ -333,31 +421,3 @@ void pa_volume_control_remove_default_for_device(pa_volume_control *control, pa_
+     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
+index aaba758..a47ab20 100644
+--- a/src/modules/volume-api/volume-control.h
++++ b/src/modules/volume-api/volume-control.h
+@@ -23,10 +23,23 @@
+ ***/
+ #include <modules/volume-api/bvolume.h>
++#include <modules/volume-api/inidb.h>
+ #include <modules/volume-api/volume-api.h>
+ typedef struct pa_volume_control pa_volume_control;
++typedef enum {
++    PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME,
++    PA_VOLUME_CONTROL_PURPOSE_OTHER,
++} pa_volume_control_purpose_t;
++
++/* Usually remapped_volume is the volume to use, because it has a matching
++ * channel map with the control, but in case the volume needs to be propagated
++ * to another control, original_volume can be used to avoid loss of precision
++ * that can result from remapping. */
++typedef int (*pa_volume_control_set_volume_cb_t)(pa_volume_control *control, const pa_bvolume *original_volume,
++                                                 const pa_bvolume *remapped_volume, bool set_volume, bool set_balance);
++
+ struct pa_volume_control {
+     pa_volume_api *volume_api;
+     uint32_t index;
+@@ -35,65 +48,61 @@ struct pa_volume_control {
+     pa_proplist *proplist;
+     pa_bvolume volume;
+     bool convertible_to_dB;
+-    bool channel_map_is_writable;
++    bool present;
++    bool persistent;
+-    /* 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_volume_control_purpose_t purpose;
++    union {
++        pas_stream *owner_stream;
++        void *owner;
++    };
+     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) */
++
++    struct {
++        pa_inidb_cell *description;
++        pa_inidb_cell *volume;
++        pa_inidb_cell *balance;
++        pa_inidb_cell *convertible_to_dB;
++    } db_cells;
+     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);
++     * expected to return a negative error code on failure. */
++    pa_volume_control_set_volume_cb_t set_volume;
+     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);
+-
++int pa_volume_control_new(pa_volume_api *api, const char *name, bool persistent, pa_volume_control **_r);
++void pa_volume_control_put(pa_volume_control *control);
+ 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 the volume control implementation, before
++ * pa_volume_control_put(). */
++void pa_volume_control_set_purpose(pa_volume_control *control, pa_volume_control_purpose_t purpose, void *owner);
+-/* Called by clients and policy modules. */
++/* Called by the volume control implementation. */
++int pa_volume_control_acquire_for_audio_group(pa_volume_control *control, pa_audio_group *group,
++                                              pa_volume_control_set_volume_cb_t set_volume_cb, void *userdata);
++
++/* Called by the volume control implementation. This must only be called for
++ * persistent controls; use pa_volume_control_free() for non-persistent
++ * controls. */
++void pa_volume_control_release(pa_volume_control *control);
++
++/* Called by anyone. */
++void pa_volume_control_set_description(pa_volume_control *control, const char *description);
+ 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);
++void pa_volume_control_set_channel_map(pa_volume_control *control, const pa_channel_map *map);
++void pa_volume_control_set_convertible_to_dB(pa_volume_control *control, bool convertible);
+ /* Called from device.c only. */
+ void pa_volume_control_add_device(pa_volume_control *control, pa_device *device);
+@@ -101,12 +110,4 @@ void pa_volume_control_remove_device(pa_volume_control *control, pa_device *devi
+ 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
+index 032e108..b81909a 100644
+--- a/src/pulse/ext-volume-api.c
++++ b/src/pulse/ext-volume-api.c
+@@ -36,6 +36,7 @@
+ #include <pulsecore/i18n.h>
+ #include <pulsecore/macro.h>
+ #include <pulsecore/pstream-util.h>
++#include <pulsecore/strbuf.h>
+ #include <math.h>
+@@ -94,6 +95,21 @@ void pa_ext_volume_api_bvolume_init_invalid(pa_ext_volume_api_bvolume *volume) {
+     pa_channel_map_init(&volume->channel_map);
+ }
++void pa_ext_volume_api_bvolume_init(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume, pa_channel_map *map) {
++    unsigned i;
++
++    pa_assert(bvolume);
++    pa_assert(PA_VOLUME_IS_VALID(volume));
++    pa_assert(map);
++    pa_assert(pa_channel_map_valid(map));
++
++    bvolume->volume = volume;
++    bvolume->channel_map = *map;
++
++    for (i = 0; i < map->channels; i++)
++        bvolume->balance[i] = 1.0;
++}
++
+ 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));
+@@ -103,6 +119,67 @@ void pa_ext_volume_api_bvolume_init_mono(pa_ext_volume_api_bvolume *bvolume, pa_
+     pa_channel_map_init_mono(&bvolume->channel_map);
+ }
++int pa_ext_volume_api_bvolume_parse_balance(const char *str, pa_ext_volume_api_bvolume *_r) {
++    pa_ext_volume_api_bvolume bvolume;
++
++    pa_assert(str);
++    pa_assert(_r);
++
++    bvolume.channel_map.channels = 0;
++
++    for (;;) {
++        const char *colon;
++        size_t channel_name_len;
++        char *channel_name;
++        pa_channel_position_t position;
++        const char *space;
++        size_t balance_str_len;
++        char *balance_str;
++        int r;
++        double balance;
++
++        colon = strchr(str, ':');
++        if (!colon)
++            return -PA_ERR_INVALID;
++
++        channel_name_len = colon - str;
++        channel_name = pa_xstrndup(str, channel_name_len);
++
++        position = pa_channel_position_from_string(channel_name);
++        pa_xfree(channel_name);
++        if (position == PA_CHANNEL_POSITION_INVALID)
++            return -PA_ERR_INVALID;
++
++        bvolume.channel_map.map[bvolume.channel_map.channels] = position;
++        str = colon + 1;
++
++        space = strchr(str, ' ');
++        if (space)
++            balance_str_len = space - str;
++        else
++            balance_str_len = strlen(str);
++
++        balance_str = pa_xstrndup(str, balance_str_len);
++
++        r = pa_atod(balance_str, &balance);
++        if (r < 0)
++            return -PA_ERR_INVALID;
++
++        if (!pa_ext_volume_api_balance_valid(balance))
++            return -PA_ERR_INVALID;
++
++        bvolume.balance[bvolume.channel_map.channels++] = balance;
++
++        if (space)
++            str = space + 1;
++        else
++            break;
++    }
++
++    pa_ext_volume_api_bvolume_copy_balance(_r, &bvolume);
++    return 0;
++}
++
+ 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;
+@@ -266,6 +343,29 @@ void pa_ext_volume_api_bvolume_set_rear_front_balance(pa_ext_volume_api_bvolume
+     volume->volume = old_volume;
+ }
++int pa_ext_volume_api_bvolume_balance_to_string(const pa_ext_volume_api_bvolume *volume, char **_r) {
++    pa_strbuf *buf;
++    unsigned i;
++
++    pa_assert(volume);
++    pa_assert(_r);
++
++    if (!pa_ext_volume_api_bvolume_valid(volume, false, true))
++        return -PA_ERR_INVALID;
++
++    buf = pa_strbuf_new();
++
++    for (i = 0; i < volume->channel_map.channels; i++) {
++        if (i != 0)
++            pa_strbuf_putc(buf, ' ');
++
++        pa_strbuf_printf(buf, "%s:%.2f", pa_channel_position_to_string(volume->channel_map.map[i]), volume->balance[i]);
++    }
++
++    *_r = pa_strbuf_tostring_free(buf);
++    return 0;
++}
++
+ char *pa_ext_volume_api_bvolume_snprint_balance(char *buf, size_t buf_len,
+                                                 const pa_ext_volume_api_bvolume *volume) {
+     char *e;
+diff --git a/src/pulse/ext-volume-api.h b/src/pulse/ext-volume-api.h
+index 720ff39..6402f4b 100644
+--- a/src/pulse/ext-volume-api.h
++++ b/src/pulse/ext-volume-api.h
+@@ -50,7 +50,9 @@ 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(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume, pa_channel_map *map);
+ void pa_ext_volume_api_bvolume_init_mono(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume);
++int pa_ext_volume_api_bvolume_parse_balance(const char *str, pa_ext_volume_api_bvolume *bvolume);
+ 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,
+@@ -64,6 +66,7 @@ double pa_ext_volume_api_bvolume_get_left_right_balance(const pa_ext_volume_api_
+ 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);
++int pa_ext_volume_api_bvolume_balance_to_string(const pa_ext_volume_api_bvolume *volume, char **_r);
+ #define PA_EXT_VOLUME_API_BVOLUME_SNPRINT_BALANCE_MAX 500
+ char *pa_ext_volume_api_bvolume_snprint_balance(char *buf, size_t buf_size,
+diff --git a/src/tizen-ivi/audio-groups.conf b/src/tizen-ivi/audio-groups.conf
+index 4839307..182df0d 100644
+--- a/src/tizen-ivi/audio-groups.conf
++++ b/src/tizen-ivi/audio-groups.conf
+@@ -1,33 +1,32 @@
+ [General]
+-audio-groups = x-tizen-ivi-call-downlink-audio-group x-tizen-ivi-navigator-output-audio-group x-tizen-ivi-default-output-audio-group
+-streams = call-downlink navigator-output default-output
++stream-rules = call-downlink navigator-output default-output
+ [AudioGroup x-tizen-ivi-call-downlink-audio-group]
+ description = Call downlink
+-volume-control = create
+-mute-control = create
++volume-control = create:call-downlink-volume-control
++mute-control = create:call-downlink-mute-control
+ [AudioGroup x-tizen-ivi-navigator-output-audio-group]
+ description = Navigator
+-volume-control = create
+-mute-control = create
++volume-control = bind:AudioGroup:x-tizen-ivi-default-output-audio-group
++mute-control = bind:AudioGroup:x-tizen-ivi-default-output-audio-group
+ [AudioGroup x-tizen-ivi-default-output-audio-group]
+ description = Default
+-volume-control = create
+-mute-control = create
++volume-control = create:default-output-volume-control
++mute-control = create:default-output-mute-control
+-[Stream call-downlink]
++[StreamRule call-downlink]
+ match = (direction output AND property media.role=phone)
+ audio-group-for-volume = x-tizen-ivi-call-downlink-audio-group
+ audio-group-for-mute = x-tizen-ivi-call-downlink-audio-group
+-[Stream navigator-output]
++[StreamRule navigator-output]
+ match = (direction output AND property media.role=navigator)
+ audio-group-for-volume = x-tizen-ivi-navigator-output-audio-group
+ audio-group-for-mute = x-tizen-ivi-navigator-output-audio-group
+-[Stream default-output]
+-match = (direction output)
++[StreamRule default-output]
++match = (direction output AND NEG property media.role=filter)
+ audio-group-for-volume = x-tizen-ivi-default-output-audio-group
+ audio-group-for-mute = x-tizen-ivi-default-output-audio-group
+diff --git a/src/tizen-ivi/main-volume-policy.conf b/src/tizen-ivi/main-volume-policy.conf
+index 0a83968..f2b3513 100644
+--- a/src/tizen-ivi/main-volume-policy.conf
++++ b/src/tizen-ivi/main-volume-policy.conf
+@@ -3,7 +3,6 @@ output-volume-model = by-active-main-volume-context
+ input-volume-model = none
+ output-mute-model = by-active-main-volume-context
+ input-mute-model = none
+-main-volume-contexts = x-tizen-ivi-call default
+ [MainVolumeContext x-tizen-ivi-call]
+ description = Call main volume context
diff --git a/recipes-multimedia/pulseaudio/pulseaudio_git.bb_old b/recipes-multimedia/pulseaudio/pulseaudio_git.bb_old
new file mode 100644 (file)
index 0000000..55db501
--- /dev/null
@@ -0,0 +1,24 @@
+require pulseaudio.inc
+
+PRIORITY = "10"
+
+S = "${WORKDIR}/git"
+
+LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-2.0;md5=801f80980d171dd6425610833a22dbe6"
+
+SRC_URI = "git://review.tizen.org/platform/upstream/pulseaudio;tag=99714e130755179217cd948661eaca8d31e041c7;nobranch=1"
+
+#SRC_URI += "file://0001-configure.ac-Check-only-for-libsystemd-not-libsystem.patch"
+SRC_URI += "file://volatiles.04_pulse"
+
+
+BBCLASSEXTEND += " native "
+
+do_compile_prepend() {
+        ${S}/bootstrap.sh
+}
+
+do_compile_prepend() {
+    mkdir -p ${S}/libltdl
+    cp ${STAGING_LIBDIR}/libltdl* ${S}/libltdl
+}