From a830a659142e439a19e11f4b79b3104695778cd7 Mon Sep 17 00:00:00 2001 From: Ronan Le Martret Date: Fri, 5 Sep 2014 09:58:03 +0200 Subject: [PATCH] Fix pulseaudio. Change-Id: Ibc8e7d6c261df47e722d16858d6dae348ada42c3 Signed-off-by: Ronan Le Martret --- .../pulseaudio/pulseaudio_5.0.bbappend | 108 +- ...ume-ramp-additions-to-the-low-level-infra.patch | 559 ++ ...02-volume-ramp-add-volume-ramping-to-sink.patch | 243 + ...-ramp-adding-volume-ramping-to-sink-input.patch | 181 + ...ys-install-files-for-a-module-development.patch | 72 + ...0005-jack-detection-fix-for-wired-headset.patch | 23 + ..._thread_mq_done-safe-for-subsequent-calls.patch | 29 + ...-manager-adding-external-node-manager-API.patch | 568 ++ ...ode-manager-adding-node-support-for-pactl.patch | 152 + ...add-internal-corking-state-for-sink-input.patch | 87 + ...th-Add-basic-support-for-HEADSET-profiles.patch | 634 ++ ...Create-Handsfree-Audio-Agent-NULL-backend.patch | 176 + ...reate-Handsfree-Audio-Agent-oFono-backend.patch | 161 + .../0013-bluetooth-Monitor-D-Bus-signals.patch | 99 + ...reate-pa_bluetooth_dbus_send_and_add_to_p.patch | 51 + ...egister-Unregister-Handsfree-Audio-Agent-.patch | 172 + ...ist-HandsfreeAudioCard-objects-from-oFono.patch | 98 + ...tooth-Parse-HandsfreeAudioCard-properties.patch | 192 + ...mplement-transport-acquire-for-hf_audio_a.patch | 48 + .../0019-bluetooth-Track-oFono-service.patch | 109 + .../0020-bluetooth-Handle-CardAdded-signal.patch | 38 + .../0021-bluetooth-Handle-CardRemoved-signal.patch | 43 + ...mplement-org.ofono.HandsfreeAudioAgent.Ne.patch | 56 + ...th-Fix-not-handle-fd-in-DEFER_SETUP-state.patch | 44 + ...h-Suspend-sink-source-on-HFP-s-stream-HUP.patch | 60 + ...mplement-transport-release-for-hf_audio_a.patch | 27 + ...h-Fixes-HFP-audio-transfer-when-initiator.patch | 59 + ...et-off-profile-as-default-for-newly-creat.patch | 37 + ...fono-and-pulseaudio-starting-order-assert.patch | 27 + .../0029-hfp-do-safe-strcmp-in-dbus-handler.patch | 23 + ...eter-to-define-key-used-in-stream-restore.patch | 258 + .../0031-increase-alsa-rewind-safeguard.patch | 39 + ...-profile-change-prototype-in-bluez5-patch.patch | 23 + .../0033-changes-to-pa-simple-api-samsung.patch | 438 + ...port-for-samsung-power-management-samsung.patch | 301 + ...d-preload-fileter-for-resample-samsung.patch.gz | Bin 0 -> 112359 bytes .../0036-Enhance-for-echo-cancel-samsung.patch | 304 + .../0037-add-support-for-dlog-samsung.patch | 285 + .../0038-add-policy-module-samsung.patch | 1317 +++ ...bluetooth-a2dp-aptx-codec-support-samsung.patch | 1024 +++ .../0040-create-pa_ready-file-samsung.patch | 40 + ...-set-alsa-suspend-timeout-to-zero-samsung.patch | 32 + ...ossible-infinite-waiting-in-startup-samsu.patch | 61 + ...043-use-udev-only-for-usb-devices-samsung.patch | 98 + ...mprovements-to-makefile-and-configure-in-.patch | 120 + .../0045-fix-warning-in-gconf-helper.patch | 24 + ...-add-client-api-support-for-volume-rampin.patch | 461 + ...7-adjust-default-bluetooth-profile-to-off.patch | 23 + ...ile_set-patch-which-fixed-bt-a2dp-hsp-pro.patch | 83 + .../0049-added-pulseaudio.service-file.patch | 27 + .../0050-.gitignore-Add-pulsecore-config.h.patch | 19 + .../0051-pactl-Fix-crash-with-older-servers.patch | 405 + ...lume-Increase-PA_SW_VOLUME_SNPRINT_DB_MAX.patch | 26 + ...-sink-input-source-output-Fix-mute-saving.patch | 37 + ...dd-a-couple-of-direction-helper-functions.patch | 180 + .../0055-core-util-Add-pa_join.patch | 52 + ...56-dynarray-Add-pa_dynarray_get_raw_array.patch | 36 + .../0057-dynarray-Add-PA_DYNARRAY_FOREACH.patch | 64 + ...0058-dynarray-Add-pa_dynarray_remove_last.patch | 51 + .../0059-dynarray-Add-pa_dynarray_remove_all.patch | 57 + ...60-hashmap-Add-pa_hashmap_remove_and_free.patch | 53 + ...061-device-port-Add-pa_device_port.active.patch | 236 + ...-Assign-to-reference_volume-from-only-one.patch | 246 + ...source-output-Assign-to-volume-from-only-.patch | 506 + ...64-sink-source-Return-early-from-set_mute.patch | 75 + ...put-source-output-Add-logging-to-set_mute.patch | 67 + ...-Allow-calling-set_mute-during-initializa.patch | 61 + ...cancel-Remove-redundant-get_mute-callback.patch | 47 + ...nk-source-Call-set_mute-from-mute_changed.patch | 133 + ...rce-Assign-to-s-muted-from-only-one-place.patch | 346 + ...source-output-Remove-redundant-get_mute-f.patch | 210 + ...nel-Remove-some-redundant-boolean-convers.patch | 37 + ...-sink-source-Add-hooks-for-volume-changes.patch | 52 + ...source-output-Add-hooks-for-volume-change.patch | 52 + ...74-sink-source-Add-hooks-for-mute-changes.patch | 56 + ...-source-output-Add-hooks-for-mute-changes.patch | 56 + ...ink-monitor-source-before-activating-port.patch | 30 + ...text-extension-Add-the-pa_extension-class.patch | 338 + .../0078-volume-api-Add-libvolume-api.so.patch.gz | Bin 0 -> 27952 bytes ...-volume-api-and-the-related-client-API.patch.gz | Bin 0 -> 13036 bytes ...-pactl-Add-support-for-the-new-volume-API.patch | 963 ++ .../0081-Add-module-audio-groups.patch | 1425 +++ .../0082-Add-module-main-volume-policy.patch | 1404 +++ ...on-Add-default-IVI-audio-group-and-main-v.patch | 119 + ...-Initialize-port-before-fixate-hook-fixes.patch | 159 + ...on-pulseaudio-tizen-configuration-in-defa.patch | 123 + .../0086-pactl-Fix-crash-in-pactl-list.patch | 26 + ...on-x-example-x-tizen-ivi-in-volume-config.patch | 173 + ...tor-stream-creator-Add-a-couple-of-assert.patch | 47 + ...0089-main-volume-policy-Fix-a-memory-leak.patch | 64 + ...roups-fix-issues-found-by-static-analysis.patch | 146 + .../0091-core-util-Add-pa_append_to_home_dir.patch | 72 + ...0092-core-util-Add-pa_get_config_home_dir.patch | 116 + ...ore-util-Add-pa_append_to_config_home_dir.patch | 49 + ...eate-the-config-home-directory-on-startup.patch | 55 + ...-unlinking-of-uninitialized-streams-grace.patch | 42 + ...andle-unlinking-of-uninitialized-streams-.patch | 26 + ...g-Handle-unlinking-of-uninitialized-strea.patch | 26 + .../0098-core-util-Add-pa_boolean_to_string.patch | 28 + ...source-output-Assign-to-reference_ratio-f.patch | 243 + ...source-output-Add-hooks-for-reference-rat.patch | 87 + ...source-output-Use-new_data.volume-only-fo.patch | 411 + ...source-output-Add-the-real-object-pointer.patch | 543 ++ ...s-main-volume-policy-volume-api-Various-f.patch | 9676 ++++++++++++++++++++ .../pulseaudio/pulseaudio_git.bb_old | 24 + 105 files changed, 28708 insertions(+), 1 deletion(-) create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0001-volume-ramp-additions-to-the-low-level-infra.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0002-volume-ramp-add-volume-ramping-to-sink.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0003-volume-ramp-adding-volume-ramping-to-sink-input.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0004-build-sys-install-files-for-a-module-development.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0005-jack-detection-fix-for-wired-headset.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0006-make-pa_thread_mq_done-safe-for-subsequent-calls.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0007-node-manager-adding-external-node-manager-API.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0008-node-manager-adding-node-support-for-pactl.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0009-add-internal-corking-state-for-sink-input.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0010-bluetooth-Add-basic-support-for-HEADSET-profiles.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0011-bluetooth-Create-Handsfree-Audio-Agent-NULL-backend.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0012-bluetooth-Create-Handsfree-Audio-Agent-oFono-backend.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0013-bluetooth-Monitor-D-Bus-signals.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0014-bluetooth-Create-pa_bluetooth_dbus_send_and_add_to_p.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0015-bluetooth-Register-Unregister-Handsfree-Audio-Agent-.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0016-bluetooth-List-HandsfreeAudioCard-objects-from-oFono.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0017-bluetooth-Parse-HandsfreeAudioCard-properties.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0018-bluetooth-Implement-transport-acquire-for-hf_audio_a.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0019-bluetooth-Track-oFono-service.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0020-bluetooth-Handle-CardAdded-signal.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0021-bluetooth-Handle-CardRemoved-signal.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0022-bluetooth-Implement-org.ofono.HandsfreeAudioAgent.Ne.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0023-bluetooth-Fix-not-handle-fd-in-DEFER_SETUP-state.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0024-bluetooth-Suspend-sink-source-on-HFP-s-stream-HUP.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0025-bluetooth-Implement-transport-release-for-hf_audio_a.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0026-bluetooth-Fixes-HFP-audio-transfer-when-initiator.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0027-bluetooth-Set-off-profile-as-default-for-newly-creat.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0028-fix-ofono-and-pulseaudio-starting-order-assert.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0029-hfp-do-safe-strcmp-in-dbus-handler.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0030-add-parameter-to-define-key-used-in-stream-restore.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0031-increase-alsa-rewind-safeguard.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0032-fix-for-profile-change-prototype-in-bluez5-patch.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0033-changes-to-pa-simple-api-samsung.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0034-add-support-for-samsung-power-management-samsung.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0035-Add-preload-fileter-for-resample-samsung.patch.gz create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0036-Enhance-for-echo-cancel-samsung.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0037-add-support-for-dlog-samsung.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0038-add-policy-module-samsung.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0039-add-bluetooth-a2dp-aptx-codec-support-samsung.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0040-create-pa_ready-file-samsung.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0041-set-alsa-suspend-timeout-to-zero-samsung.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0042-cope-with-possible-infinite-waiting-in-startup-samsu.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0043-use-udev-only-for-usb-devices-samsung.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0044-fixes-and-improvements-to-makefile-and-configure-in-.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0045-fix-warning-in-gconf-helper.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0046-volume-ramp-add-client-api-support-for-volume-rampin.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0047-adjust-default-bluetooth-profile-to-off.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0048-Add-bt_profile_set-patch-which-fixed-bt-a2dp-hsp-pro.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0049-added-pulseaudio.service-file.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0050-.gitignore-Add-pulsecore-config.h.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0051-pactl-Fix-crash-with-older-servers.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0052-volume-Increase-PA_SW_VOLUME_SNPRINT_DB_MAX.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0053-sink-input-source-output-Fix-mute-saving.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0054-direction-Add-a-couple-of-direction-helper-functions.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0055-core-util-Add-pa_join.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0056-dynarray-Add-pa_dynarray_get_raw_array.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0057-dynarray-Add-PA_DYNARRAY_FOREACH.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0058-dynarray-Add-pa_dynarray_remove_last.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0059-dynarray-Add-pa_dynarray_remove_all.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0060-hashmap-Add-pa_hashmap_remove_and_free.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0061-device-port-Add-pa_device_port.active.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0062-sink-source-Assign-to-reference_volume-from-only-one.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0063-sink-input-source-output-Assign-to-volume-from-only-.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0064-sink-source-Return-early-from-set_mute.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0065-sink-input-source-output-Add-logging-to-set_mute.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0066-sink-source-Allow-calling-set_mute-during-initializa.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0067-echo-cancel-Remove-redundant-get_mute-callback.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0068-sink-source-Call-set_mute-from-mute_changed.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0069-sink-source-Assign-to-s-muted-from-only-one-place.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0070-sink-input-source-output-Remove-redundant-get_mute-f.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0071-solaris-tunnel-Remove-some-redundant-boolean-convers.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0072-sink-source-Add-hooks-for-volume-changes.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0073-sink-input-source-output-Add-hooks-for-volume-change.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0074-sink-source-Add-hooks-for-mute-changes.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0075-sink-input-source-output-Add-hooks-for-mute-changes.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0076-sink-Link-monitor-source-before-activating-port.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0077-context-extension-Add-the-pa_extension-class.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0078-volume-api-Add-libvolume-api.so.patch.gz create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0079-Add-module-volume-api-and-the-related-client-API.patch.gz create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0080-pactl-Add-support-for-the-new-volume-API.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0081-Add-module-audio-groups.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0082-Add-module-main-volume-policy.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0083-configuration-Add-default-IVI-audio-group-and-main-v.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0084-sink-source-Initialize-port-before-fixate-hook-fixes.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0085-configuration-pulseaudio-tizen-configuration-in-defa.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0086-pactl-Fix-crash-in-pactl-list.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0087-configuration-x-example-x-tizen-ivi-in-volume-config.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0088-device-creator-stream-creator-Add-a-couple-of-assert.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0089-main-volume-policy-Fix-a-memory-leak.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0090-audio-groups-fix-issues-found-by-static-analysis.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0091-core-util-Add-pa_append_to_home_dir.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0092-core-util-Add-pa_get_config_home_dir.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0093-core-util-Add-pa_append_to_config_home_dir.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0094-core-Create-the-config-home-directory-on-startup.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0095-alsa-Handle-unlinking-of-uninitialized-streams-grace.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0096-role-cork-Handle-unlinking-of-uninitialized-streams-.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0097-role-ducking-Handle-unlinking-of-uninitialized-strea.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0098-core-util-Add-pa_boolean_to_string.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0099-sink-input-source-output-Assign-to-reference_ratio-f.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0100-sink-input-source-output-Add-hooks-for-reference-rat.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0101-sink-input-source-output-Use-new_data.volume-only-fo.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0102-sink-input-source-output-Add-the-real-object-pointer.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_5.0/0103-audio-groups-main-volume-policy-volume-api-Various-f.patch create mode 100644 recipes-multimedia/pulseaudio/pulseaudio_git.bb_old diff --git a/recipes-multimedia/pulseaudio/pulseaudio_5.0.bbappend b/recipes-multimedia/pulseaudio/pulseaudio_5.0.bbappend index 1c2694a..017b273 100644 --- a/recipes-multimedia/pulseaudio/pulseaudio_5.0.bbappend +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0.bbappend @@ -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 index 0000000..8b2693c --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0001-volume-ramp-additions-to-the-low-level-infra.patch @@ -0,0 +1,559 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..4ea7c20 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0002-volume-ramp-add-volume-ramping-to-sink.patch @@ -0,0 +1,243 @@ +From: Jaska Uimonen +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 +--- + 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 + #include + #include ++#include + #include + #include + #include +@@ -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 index 0000000..9557dc8 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0003-volume-ramp-adding-volume-ramping-to-sink-input.patch @@ -0,0 +1,181 @@ +From: Jaska Uimonen +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 +--- + 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 + #include + #include ++#include + + 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 index 0000000..8b43385 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0004-build-sys-install-files-for-a-module-development.patch @@ -0,0 +1,72 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..002e4ec --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0005-jack-detection-fix-for-wired-headset.patch @@ -0,0 +1,23 @@ +From: Jaska Uimonen +Date: Sun, 10 Jun 2012 15:13:11 +0300 +Subject: jack detection fix for wired headset + +Change-Id: I53d465bf56adc2e3e5551b43d59ff99b63bc76cc +Signed-off-by: Jaska Uimonen +--- + 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 index 0000000..bfbf3d9 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0006-make-pa_thread_mq_done-safe-for-subsequent-calls.patch @@ -0,0 +1,29 @@ +From: Janos Kovacs +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 +--- + 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 index 0000000..5deba2b --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0007-node-manager-adding-external-node-manager-API.patch @@ -0,0 +1,568 @@ +From: Ismo Puustinen +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 +--- + 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 ++#endif ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#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 ++#include ++#include ++ ++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 + #include + #include ++#include + + #include + #include +@@ -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 index 0000000..64cc9c3 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0008-node-manager-adding-node-support-for-pactl.patch @@ -0,0 +1,152 @@ +From: Jaska Uimonen +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 +--- + 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 + #include ++#include + + #include + #include +@@ -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 index 0000000..3ff665d --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0009-add-internal-corking-state-for-sink-input.patch @@ -0,0 +1,87 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..3249f6f --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0010-bluetooth-Add-basic-support-for-HEADSET-profiles.patch @@ -0,0 +1,634 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 + + #include ++#include + #include + #include + #include +@@ -59,7 +60,9 @@ PA_MODULE_USAGE("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 index 0000000..0ead373 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0011-bluetooth-Create-Handsfree-Audio-Agent-NULL-backend.patch @@ -0,0 +1,176 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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=],[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 + + #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 ++#endif ++ ++#include ++ ++#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 ++ ++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 index 0000000..39b938b --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0012-bluetooth-Create-Handsfree-Audio-Agent-oFono-backend.patch @@ -0,0 +1,161 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 ++#endif ++ ++#include ++#include ++ ++#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 \ ++ "" \ ++ " " \ ++ " " \ ++ " " \ ++ " " \ ++ " " \ ++ " " \ ++ " " \ ++ " " \ ++ " " \ ++ " " \ ++ " " \ ++ " " \ ++ " " \ ++ " " \ ++ "" ++ ++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 index 0000000..0a93d5c --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0013-bluetooth-Monitor-D-Bus-signals.patch @@ -0,0 +1,99 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 index 0000000..c9d893c --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0014-bluetooth-Create-pa_bluetooth_dbus_send_and_add_to_p.patch @@ -0,0 +1,51 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 index 0000000..931069c --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0015-bluetooth-Register-Unregister-Handsfree-Audio-Agent-.patch @@ -0,0 +1,172 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 index 0000000..d943ce4 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0016-bluetooth-List-HandsfreeAudioCard-objects-from-oFono.patch @@ -0,0 +1,98 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 index 0000000..f7a2174 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0017-bluetooth-Parse-HandsfreeAudioCard-properties.patch @@ -0,0 +1,192 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 + #include ++#include ++ ++#include "bluez5-util.h" + + #include "hfaudioagent.h" + +@@ -56,9 +59,21 @@ + " " \ + "" + ++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 index 0000000..c3815cc --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0018-bluetooth-Implement-transport-acquire-for-hf_audio_a.patch @@ -0,0 +1,48 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 index 0000000..5f8fa21 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0019-bluetooth-Track-oFono-service.patch @@ -0,0 +1,109 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 index 0000000..f3aaf33 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0020-bluetooth-Handle-CardAdded-signal.patch @@ -0,0 +1,38 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 index 0000000..fbd766e --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0021-bluetooth-Handle-CardRemoved-signal.patch @@ -0,0 +1,43 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 index 0000000..4ad62dc --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0022-bluetooth-Implement-org.ofono.HandsfreeAudioAgent.Ne.patch @@ -0,0 +1,56 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 index 0000000..587fce6 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0023-bluetooth-Fix-not-handle-fd-in-DEFER_SETUP-state.patch @@ -0,0 +1,44 @@ +From: Luiz Augusto von Dentz +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 + #endif + ++#include ++ + #include + #include + #include +@@ -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 index 0000000..5858d67 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0024-bluetooth-Suspend-sink-source-on-HFP-s-stream-HUP.patch @@ -0,0 +1,60 @@ +From: =?utf-8?q?Jo=C3=A3o_Paulo_Rechi_Vita?= +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 index 0000000..b557ea5 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0025-bluetooth-Implement-transport-release-for-hf_audio_a.patch @@ -0,0 +1,27 @@ +From: Luiz Augusto von Dentz +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 index 0000000..4dd9742 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0026-bluetooth-Fixes-HFP-audio-transfer-when-initiator.patch @@ -0,0 +1,59 @@ +From: Luiz Augusto von Dentz +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 index 0000000..cce0497 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0027-bluetooth-Set-off-profile-as-default-for-newly-creat.patch @@ -0,0 +1,37 @@ +From: Luiz Augusto von Dentz +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 index 0000000..db44a2e --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0028-fix-ofono-and-pulseaudio-starting-order-assert.patch @@ -0,0 +1,27 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..8402ffa --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0029-hfp-do-safe-strcmp-in-dbus-handler.patch @@ -0,0 +1,23 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..0773d40 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0030-add-parameter-to-define-key-used-in-stream-restore.patch @@ -0,0 +1,258 @@ +From: Jaska Uimonen +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 +--- + 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= " + "on_hotplug= " + "on_rescue= " +- "fallback_table="); ++ "fallback_table=" ++ "preferred_stream_group= "); + + #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 index 0000000..eac1811 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0031-increase-alsa-rewind-safeguard.patch @@ -0,0 +1,39 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..646694a --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0032-fix-for-profile-change-prototype-in-bluez5-patch.patch @@ -0,0 +1,23 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..e041892 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0033-changes-to-pa-simple-api-samsung.patch @@ -0,0 +1,438 @@ +From: "vivian,zhang" +Date: Tue, 18 Jun 2013 16:10:15 +0800 +Subject: changes to pa simple api - samsung + +Change-Id: I997c02217a8dc14524480164aa0baeea901c7b4e +Signed-off-by: Jaska Uimonen +--- + 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 + #include + ++#include + #include + #include + + #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 + #include + ++#include + /** \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 index 0000000..5d2380b --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0034-add-support-for-samsung-power-management-samsung.patch @@ -0,0 +1,301 @@ +From: "vivian,zhang" +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 +--- + 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 + #include + ++#include + #include "module-suspend-on-idle-symdef.h" ++//move to configure.ac ++//#define USE_PM_LOCK /* Enable as default */ ++#ifdef USE_PM_LOCK ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 index 0000000000000000000000000000000000000000..a8f29d726bc37575518d87cc265ced95c75f8038 GIT binary patch literal 112359 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

d)SuOEk?yk7+wOFFExGFH; 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 Z8oKyhM#h6IKF)^ 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 literal 0 HcmV?d00001 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 index 0000000..19bbb16 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0036-Enhance-for-echo-cancel-samsung.patch @@ -0,0 +1,304 @@ +From: "vivian,zhang" +Date: Tue, 18 Jun 2013 16:18:58 +0800 +Subject: Enhance for echo cancel - samsung + +Change-Id: Ibd59e7e033d5a6789ddc7d5ef39e23f26dcf55cc +Signed-off-by: Jaska Uimonen +--- + 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 + #include + ++#include ++#include ++ + #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 ++#endif ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#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 ++#include ++ ++/** \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 index 0000000..33c38c3 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0037-add-support-for-dlog-samsung.patch @@ -0,0 +1,285 @@ +From: "vivian,zhang" +Date: Tue, 18 Jun 2013 16:20:04 +0800 +Subject: add support for dlog - samsung + +Change-Id: Ieddf2f3bdab50926372e9e2b5cedb2756b6cfd5c +Signed-off-by: Jaska Uimonen +--- + 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:', 'newfile:'.")); ++#elif defined(USE_DLOG) ++ pa_log(_("Invalid log target: use either 'syslog', 'stderr' or 'auto' or a valid file name 'file:', 'newfile:' or 'dlog' or 'dlog-color'.")); + #else + pa_log(_("Invalid log target: use either 'syslog', 'stderr' or 'auto' or a valid file name 'file:', 'newfile:'.")); + #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 ++#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 index 0000000..fb976b1 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0038-add-policy-module-samsung.patch @@ -0,0 +1,1317 @@ +From: "vivian,zhang" +Date: Tue, 18 Jun 2013 16:21:32 +0800 +Subject: add policy module - samsung + +Change-Id: I2111a9c4dc0a371dbea5b347cf77adbe8f930528 +Signed-off-by: Jaska Uimonen +--- + 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 ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include // 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= "); ++ ++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 ++#endif ++ ++#include ++#include ++#include ++ ++#include ++#include ++ ++#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 ++#include ++ ++/** \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 index 0000000..1355048 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0039-add-bluetooth-a2dp-aptx-codec-support-samsung.patch @@ -0,0 +1,1024 @@ +From: "vivian,zhang" +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 +--- + 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 ++ ++#include ++#include ++ ++#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 + #endif ++#ifdef BLUETOOTH_APTX_SUPPORT ++#include ++#endif + + #include + +@@ -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="); ++#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 + #include + #include ++#ifdef BLUETOOTH_APTX_SUPPORT ++#include ++#endif + + #include + #include +@@ -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 index 0000000..191e70e --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0040-create-pa_ready-file-samsung.patch @@ -0,0 +1,40 @@ +From: Jaska Uimonen +Date: Thu, 8 Aug 2013 11:23:38 +0300 +Subject: create pa_ready file - samsung + +Change-Id: I2146599f2e814be064864f8ca76879b761642f11 +Signed-off-by: Jaska Uimonen +--- + 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 + #include + ++#include ++ + #ifdef HAVE_SYS_MMAN_H + #include + #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 index 0000000..9c6c9a4 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0041-set-alsa-suspend-timeout-to-zero-samsung.patch @@ -0,0 +1,32 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..4e91da4 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0042-cope-with-possible-infinite-waiting-in-startup-samsu.patch @@ -0,0 +1,61 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..d989b3f --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0043-use-udev-only-for-usb-devices-samsung.patch @@ -0,0 +1,98 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..0cb735b --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0044-fixes-and-improvements-to-makefile-and-configure-in-.patch @@ -0,0 +1,120 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..b89ec5a --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0045-fix-warning-in-gconf-helper.patch @@ -0,0 +1,24 @@ +From: Jaska Uimonen +Date: Tue, 11 Mar 2014 12:47:52 +0200 +Subject: fix warning in gconf helper + +Change-Id: Id75cd24cfd1c0d62d4c227b6715dc0d9d5ea6b1f +Signed-off-by: Jaska Uimonen +--- + 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 index 0000000..9cc07b6 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0046-volume-ramp-add-client-api-support-for-volume-rampin.patch @@ -0,0 +1,461 @@ +From: Jaska Uimonen +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 +--- + 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 index 0000000..4f20de2 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0047-adjust-default-bluetooth-profile-to-off.patch @@ -0,0 +1,23 @@ +From: Jaska Uimonen +Date: Wed, 14 Aug 2013 15:00:52 +0300 +Subject: adjust default bluetooth profile to off + +Change-Id: I95ca525b0d20c1a864a0c66060767bb4c2160400 +Signed-off-by: Jaska Uimonen +--- + 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 index 0000000..83227f7 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0048-Add-bt_profile_set-patch-which-fixed-bt-a2dp-hsp-pro.patch @@ -0,0 +1,83 @@ +From: "vivian,zhang" +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 +--- + 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 index 0000000..c4c125c --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0049-added-pulseaudio.service-file.patch @@ -0,0 +1,27 @@ +From: Jaska Uimonen +Date: Tue, 11 Jun 2013 17:03:27 +0300 +Subject: added pulseaudio.service file + +Change-Id: I8efef41060189f116be49b3455c588d67f045f82 +Signed-off-by: Jaska Uimonen +--- + 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 index 0000000..c5c1d3b --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0050-.gitignore-Add-pulsecore-config.h.patch @@ -0,0 +1,19 @@ +From: Tanu Kaskinen +Date: Mon, 24 Feb 2014 12:38:31 +0200 +Subject: .gitignore: Add pulsecore-config.h + +Change-Id: I8409f1964e65e79669eaeb4930c6d536417a0b05 +Signed-off-by: Jaska Uimonen +--- + .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 index 0000000..cd4736c --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0051-pactl-Fix-crash-with-older-servers.patch @@ -0,0 +1,405 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..32330d6 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0052-volume-Increase-PA_SW_VOLUME_SNPRINT_DB_MAX.patch @@ -0,0 +1,26 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..fc389f6 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0053-sink-input-source-output-Fix-mute-saving.patch @@ -0,0 +1,37 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..3282596 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0054-direction-Add-a-couple-of-direction-helper-functions.patch @@ -0,0 +1,180 @@ +From: Tanu Kaskinen +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 +--- + 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 ++ ++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 ++ ++/** \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 + #include + #include + #include +@@ -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 index 0000000..d510f44 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0055-core-util-Add-pa_join.patch @@ -0,0 +1,52 @@ +From: Tanu Kaskinen +Date: Wed, 26 Mar 2014 13:15:12 +0200 +Subject: core-util: Add pa_join() + +Change-Id: I84ac0ee7a3097fce8ed9bad26b210fc97db9e9a7 +Signed-off-by: Jaska Uimonen +--- + 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 index 0000000..cf2c694 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0056-dynarray-Add-pa_dynarray_get_raw_array.patch @@ -0,0 +1,36 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..64d8ce5 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0057-dynarray-Add-PA_DYNARRAY_FOREACH.patch @@ -0,0 +1,64 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..a6e0f56 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0058-dynarray-Add-pa_dynarray_remove_last.patch @@ -0,0 +1,51 @@ +From: Tanu Kaskinen +Date: Wed, 21 May 2014 11:32:09 +0300 +Subject: dynarray: Add pa_dynarray_remove_last() + +Change-Id: I9098df96aac57a3ee2084061aa174f7ee02b1588 +Signed-off-by: Jaska Uimonen +--- + 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 index 0000000..d2e66b5 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0059-dynarray-Add-pa_dynarray_remove_all.patch @@ -0,0 +1,57 @@ +From: Tanu Kaskinen +Date: Wed, 21 May 2014 11:36:19 +0300 +Subject: dynarray: Add pa_dynarray_remove_all() + +Change-Id: I35079f8fe4b361221a1bdc1fececbe318bf3ee0e +Signed-off-by: Jaska Uimonen +--- + 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 index 0000000..09ab108 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0060-hashmap-Add-pa_hashmap_remove_and_free.patch @@ -0,0 +1,53 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..ef71747 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0061-device-port-Add-pa_device_port.active.patch @@ -0,0 +1,236 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..2f5a35c --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0062-sink-source-Assign-to-reference_volume-from-only-one.patch @@ -0,0 +1,246 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..38b3293 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0063-sink-input-source-output-Assign-to-volume-from-only-.patch @@ -0,0 +1,506 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..4cd0fdb --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0064-sink-source-Return-early-from-set_mute.patch @@ -0,0 +1,75 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..a60d1d2 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0065-sink-input-source-output-Add-logging-to-set_mute.patch @@ -0,0 +1,67 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..02ac7b5 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0066-sink-source-Allow-calling-set_mute-during-initializa.patch @@ -0,0 +1,61 @@ +From: Tanu Kaskinen +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 Signed-off-by: Jaska Uimonen +--- + 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 index 0000000..4962b8d --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0067-echo-cancel-Remove-redundant-get_mute-callback.patch @@ -0,0 +1,47 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..42422ea --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0068-sink-source-Call-set_mute-from-mute_changed.patch @@ -0,0 +1,133 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..c7332fd --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0069-sink-source-Assign-to-s-muted-from-only-one-place.patch @@ -0,0 +1,346 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..9b25a3d --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0070-sink-input-source-output-Remove-redundant-get_mute-f.patch @@ -0,0 +1,210 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..4b4acfe --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0071-solaris-tunnel-Remove-some-redundant-boolean-convers.patch @@ -0,0 +1,37 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..08fbc83 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0072-sink-source-Add-hooks-for-volume-changes.patch @@ -0,0 +1,52 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..9cf1c67 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0073-sink-input-source-output-Add-hooks-for-volume-change.patch @@ -0,0 +1,52 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..ddd8514 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0074-sink-source-Add-hooks-for-mute-changes.patch @@ -0,0 +1,56 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..2607c14 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0075-sink-input-source-output-Add-hooks-for-mute-changes.patch @@ -0,0 +1,56 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..e2de6d6 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0076-sink-Link-monitor-source-before-activating-port.patch @@ -0,0 +1,30 @@ +From: Tanu Kaskinen +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 +--- + 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 index 0000000..a2a5141 --- /dev/null +++ b/recipes-multimedia/pulseaudio/pulseaudio_5.0/0077-context-extension-Add-the-pa_extension-class.patch @@ -0,0 +1,338 @@ +From: Tanu Kaskinen +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 +--- + 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 ++#endif ++ ++#include "extension.h" ++ ++#include ++ ++#include ++#include ++ ++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 ++ ++#include ++ ++#include ++ ++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 + #include + #include + #include +@@ -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 index 0000000000000000000000000000000000000000..465ef386b66c40263da3b0d58dff2179d153a06d GIT binary patch literal 27952 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 u`81C^E +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 +--- + 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. + +

list [short] [TYPE]

+-

Dump all currently loaded modules, available sinks, sources, streams, etc. TYPE 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.

++

Dump all currently loaded modules, available sinks, sources, ++ streams, etc. TYPE 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.

+ + + ++

++ ++ ++ ++ ++ ++ ++ + +