From bb0882c5dad030f676e424265ebcd869bb3ff899 Mon Sep 17 00:00:00 2001 From: Jan-Simon Möller Date: Mon, 16 Apr 2018 22:14:52 +0200 Subject: Introduce meta-agl-profile-core and meta-agl-profile-graphics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework towards agl profiles. This change is part of a series of changes to create the AGL profiles. This set will mainly introduce the 'core' profile. It is setup to be a drop-in change, thus some files were kept in (dummy) locations for now. However, they'll be taken care of in the next changes in this series. The main target of the meta-agl-profile-core layer is to host: - a minimal, bootable image with network and package management enabled -- agl-image-boot - a minimal image with network and packagemanagement and the AGL APIs -- agl-image-minimal The layer meta-agl-profile-graphical is used as superset of these and includes support for egl+wayland+weston. All recipes concerning graphics were moved there. This is not a full profile as we still have to migrate some parts of meta-agl-demo in a follow-up changeset. The roadmap as discussed during the F2F session in Karlsruhe is: - week 16 : core profile and profiles w/o graphics - week 17 : graphical profiles - week 18 : final conversion of the demo image v2: moved agl-login-manager from -graphics to -core (see Jose's comment) v3: moved back after discussion - follow-up in separate changeset Change-Id: Idacb0d1274baac1f63f8d1b850d4b1104ac33918 Signed-off-by: Jan-Simon Möller --- .../pulseaudio/agl-audio-plugin_0.1.bb | 28 + ...01-install-files-for-a-module-development.patch | 78 + ...ume-ramp-additions-to-the-low-level-infra.patch | 566 ++ ...-ramp-adding-volume-ramping-to-sink-input.patch | 189 + ...put-Code-cleanup-regarding-volume-ramping.patch | 62 + ...volume-Add-support-for-volume-ramp-factor.patch | 299 + ...nput-Remove-pa_sink_input_set_volume_ramp.patch | 63 + .../pulseaudio-10.0/enable-ofono-hfp-backend.patch | 11 + ...08-install-files-for-a-module-development.patch | 53 + ...ume-ramp-additions-to-the-low-level-infra.patch | 503 ++ ...-ramp-adding-volume-ramping-to-sink-input.patch | 159 + ...12-volume-ramp-add-volume-ramping-to-sink.patch | 159 + ...add-internal-corking-state-for-sink-input.patch | 72 + .../pulseaudio/0020-core-util-Add-pa_join.patch | 37 + ...21-dynarray-Add-pa_dynarray_get_raw_array.patch | 21 + ...022-device-port-Add-pa_device_port.active.patch | 198 + .../0030-volume-api-Add-libvolume-api.patch | 6118 ++++++++++++++++++++ .../0031-Add-module-main-volume-policy.patch | 1418 +++++ .../0039-main-volume-policy-adapt-to-pa6rev.patch | 11 + .../pulseaudio/pulseaudio_%.bbappend | 28 + .../pulseaudio/pulseaudio_10.0.bbappend | 15 + 21 files changed, 10088 insertions(+) create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/agl-audio-plugin_0.1.bb create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0001-install-files-for-a-module-development.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0002-volume-ramp-additions-to-the-low-level-infra.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0003-volume-ramp-adding-volume-ramping-to-sink-input.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0004-sink-input-Code-cleanup-regarding-volume-ramping.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0005-sink-input-volume-Add-support-for-volume-ramp-factor.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0006-sink-input-Remove-pa_sink_input_set_volume_ramp.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/enable-ofono-hfp-backend.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0008-install-files-for-a-module-development.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0010-volume-ramp-additions-to-the-low-level-infra.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0011-volume-ramp-adding-volume-ramping-to-sink-input.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0012-volume-ramp-add-volume-ramping-to-sink.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0013-add-internal-corking-state-for-sink-input.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0020-core-util-Add-pa_join.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0021-dynarray-Add-pa_dynarray_get_raw_array.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0022-device-port-Add-pa_device_port.active.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0030-volume-api-Add-libvolume-api.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0031-Add-module-main-volume-policy.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0039-main-volume-policy-adapt-to-pa6rev.patch create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio_%.bbappend create mode 100644 meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio_10.0.bbappend (limited to 'meta-agl-profile-core/recipes-multimedia/pulseaudio') diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/agl-audio-plugin_0.1.bb b/meta-agl-profile-core/recipes-multimedia/pulseaudio/agl-audio-plugin_0.1.bb new file mode 100644 index 000000000..633a3e248 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/agl-audio-plugin_0.1.bb @@ -0,0 +1,28 @@ +SUMMARY = "AGL Audio Policy Plugin" +DESCRIPTION = "AGL PulseAudio Routing plugin, forked from the Tizen IVI \ +PulseAudio Routing plugin, also known as module-murphy-ivi. This is a \ +stripped-down version of the former, not needing Murphy anymore and using \ +either a JSON configuration file or its own embedded configuration." +HOMEPAGE = "http://www.iot.bzh" + +LICENSE = "LGPL-2.1" +LIC_FILES_CHKSUM = "file://COPYING;md5=2d5025d4aa3495befef8f17206a5b0a1" + +DEPENDS = "json-c pulseaudio" +RDEPENDS_${PN} = "pulseaudio-server pulseaudio-module-null-sink pulseaudio-module-loopback" + +SRCREV = "952d404e87ca6001e546fe9105bdb6760c468760" +SRC_URI = "git://gerrit.automotivelinux.org/gerrit/staging/agl-audio-plugin;protocol=https;branch=${AGL_BRANCH}" + +S = "${WORKDIR}/git" + +inherit cmake pkgconfig + +FULL_OPTIMIZATION = "-O1 -pipe ${DEBUG_FLAGS}" + +PULSE_PV="9.0" + +EXTRA_OECMAKE_append = " -DPULSE_PV:STRING=${PULSE_PV}" + +FILES_${PN} += "${libdir}/pulse-${PULSE_PV}/modules/* ${sysconfdir}/pulse/*" +FILES_${PN}-dbg += "${libdir}/pulse-${PULSE_PV}/modules/.debug/*" diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0001-install-files-for-a-module-development.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0001-install-files-for-a-module-development.patch new file mode 100644 index 000000000..8c5f9efd4 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0001-install-files-for-a-module-development.patch @@ -0,0 +1,78 @@ +From 53281e2d11f84e2dae0704e0167369710ee2cb30 Mon Sep 17 00:00:00 2001 +From: Yannick Gicquel +Date: Fri, 23 Sep 2016 14:26:03 +0200 +Subject: [PATCH 1/6] install files for a module development + +Signed-off-by: Yannick Gicquel +--- + Makefile.am | 14 +++++++++++++- + configure.ac | 1 + + pulseaudio-module-devel.pc.in | 12 ++++++++++++ + 3 files changed, 26 insertions(+), 1 deletion(-) + create mode 100644 pulseaudio-module-devel.pc.in + +diff --git a/Makefile.am b/Makefile.am +index 13bc469..f0d68a2 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -53,7 +53,16 @@ dist_vapi_DATA = \ + vala/libpulse-simple.deps vala/libpulse-simple.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 $(top_srcdir)/src/pulsecore/*.h ++moduledevdir = $(includedir)/pulsemodule/pulsecore ++ ++moduledevfilter_DATA = $(top_srcdir)/src/pulsecore/filter/*.h ++moduledevfilterdir = $(includedir)/pulsemodule/pulsecore/filter ++ ++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 += \ +@@ -107,6 +116,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 9250c05..f9201ee 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1502,6 +1502,7 @@ man/pulse-client.conf.5.xml + man/default.pa.5.xml + man/pulse-cli-syntax.5.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 +-- +1.9.1 + diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0002-volume-ramp-additions-to-the-low-level-infra.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0002-volume-ramp-additions-to-the-low-level-infra.patch new file mode 100644 index 000000000..9cee6f5de --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0002-volume-ramp-additions-to-the-low-level-infra.patch @@ -0,0 +1,566 @@ +From 7757059ffc6e63ea20ba9013682d72d619e7aefc Mon Sep 17 00:00:00 2001 +From: Sangchul Lee +Date: Sat, 27 Aug 2016 21:33:16 +0900 +Subject: [PATCH 2/6] volume ramp: additions to the low level infra + +The original patch is + - https://review.tizen.org/git/?p=platform/upstream/pulseaudio.git;a=commit;h=df1c4275ed79e0b708c75b92f9d247e0492bc1f0 + - by Jaska Uimonen helsinki.fi> + +Signed-off-by: Sangchul Lee +--- + 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 93a62b8..ef9b57d 100644 +--- a/src/map-file ++++ b/src/map-file +@@ -138,6 +138,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 680bdc9..bc3cedd 100644 +--- a/src/pulse/def.h ++++ b/src/pulse/def.h +@@ -347,11 +347,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 */ +@@ -380,6 +384,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 */ + +@@ -1047,6 +1052,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 1667b94..85072c1 100644 +--- a/src/pulse/volume.c ++++ b/src/pulse/volume.c +@@ -445,7 +445,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++) +@@ -977,3 +980,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 8cf4fa4..2ae3451 100644 +--- a/src/pulse/volume.h ++++ b/src/pulse/volume.h +@@ -431,6 +431,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 59622d7..1e4cc1e 100644 +--- a/src/pulsecore/mix.c ++++ b/src/pulsecore/mix.c +@@ -724,3 +724,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 8102bcd..0f86b6f 100644 +--- a/src/pulsecore/mix.h ++++ b/src/pulsecore/mix.h +@@ -59,4 +59,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 +-- +1.9.1 + diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0003-volume-ramp-adding-volume-ramping-to-sink-input.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0003-volume-ramp-adding-volume-ramping-to-sink-input.patch new file mode 100644 index 000000000..eb485ca7c --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0003-volume-ramp-adding-volume-ramping-to-sink-input.patch @@ -0,0 +1,189 @@ +From e4469df7c69316b49cad93dd288badc98fa1cad5 Mon Sep 17 00:00:00 2001 +From: Sangchul Lee +Date: Sat, 27 Aug 2016 21:33:17 +0900 +Subject: [PATCH 3/6] volume ramp: adding volume ramping to sink-input + +The original patch is + - https://review.tizen.org/git/?p=platform/upstream/pulseaudio.git;a=commit;h=98042248fd67ce0ab3807c5c472c0d5d8b0f99d3 + - by Jaska Uimonen helsinki.fi> + +Signed-off-by: Sangchul Lee +--- + src/pulsecore/sink-input.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++ + src/pulsecore/sink-input.h | 11 ++++++++- + 2 files changed, 71 insertions(+), 1 deletion(-) + +diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c +index 8ec63b5..cc8953f 100644 +--- a/src/pulsecore/sink-input.c ++++ b/src/pulsecore/sink-input.c +@@ -526,6 +526,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->sample_spec.channels); ++ else ++ pa_cvolume_ramp_int_init(&i->ramp, PA_VOLUME_NORM, data->sample_spec.channels); ++ + i->thread_info.state = i->state; + i->thread_info.attached = false; + pa_atomic_store(&i->thread_info.drained, 1); +@@ -542,6 +547,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); + +@@ -923,6 +930,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; ++ pa_bool_t tmp; + + wchunk = tchunk; + pa_memblock_ref(wchunk.memblock); +@@ -959,6 +968,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; +@@ -975,6 +994,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); + } +@@ -1339,6 +1368,31 @@ 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, ++ pa_bool_t send_msg, ++ pa_bool_t 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); +@@ -1932,6 +1986,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 86deab2..6e1b211 100644 +--- a/src/pulsecore/sink-input.h ++++ b/src/pulsecore/sink-input.h +@@ -32,6 +32,7 @@ + #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 */ +@@ -58,7 +59,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 { +@@ -121,6 +123,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 +@@ -249,6 +254,8 @@ struct pa_sink_input { + pa_usec_t requested_sink_latency; + + pa_hashmap *direct_outputs; ++ ++ pa_cvolume_ramp_int ramp; + } thread_info; + + void *userdata; +@@ -265,6 +272,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 + }; + +@@ -370,6 +378,7 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool s + void pa_sink_input_add_volume_factor(pa_sink_input *i, const char *key, const pa_cvolume *volume_factor); + 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_volume_ramp(pa_sink_input *i, const pa_cvolume_ramp *ramp, pa_bool_t send_msg, pa_bool_t save); + + void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save); + +-- +1.9.1 + diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0004-sink-input-Code-cleanup-regarding-volume-ramping.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0004-sink-input-Code-cleanup-regarding-volume-ramping.patch new file mode 100644 index 000000000..64d7b141d --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0004-sink-input-Code-cleanup-regarding-volume-ramping.patch @@ -0,0 +1,62 @@ +From 49d8943b151a73be3dd726561a720c0f4bfdccac Mon Sep 17 00:00:00 2001 +From: Sangchul Lee +Date: Sat, 27 Aug 2016 21:33:18 +0900 +Subject: [PATCH 4/6] sink-input: Code cleanup regarding volume ramping + +Remove unused parameter of pa_sink_input_set_volume_ramp(). +Use bool instead of pa_bool_t. + +Signed-off-by: Sangchul Lee +--- + src/pulsecore/sink-input.c | 7 ++----- + src/pulsecore/sink-input.h | 2 +- + 2 files changed, 3 insertions(+), 6 deletions(-) + +diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c +index cc8953f..e1968e0 100644 +--- a/src/pulsecore/sink-input.c ++++ b/src/pulsecore/sink-input.c +@@ -931,7 +931,6 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink bytes */, pa + pa_memchunk wchunk; + bool nvfs = need_volume_factor_sink; + pa_cvolume target; +- pa_bool_t tmp; + + wchunk = tchunk; + pa_memblock_ref(wchunk.memblock); +@@ -972,7 +971,7 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink bytes */, pa + 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)))) { ++ } else if ((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); +@@ -1372,9 +1371,7 @@ int pa_sink_input_remove_volume_factor(pa_sink_input *i, const char *key) { + void pa_sink_input_set_volume_ramp( + pa_sink_input *i, + const pa_cvolume_ramp *ramp, +- pa_bool_t send_msg, +- pa_bool_t save) { +- ++ bool send_msg) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); +diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h +index 6e1b211..92f61c3 100644 +--- a/src/pulsecore/sink-input.h ++++ b/src/pulsecore/sink-input.h +@@ -378,7 +378,7 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool s + void pa_sink_input_add_volume_factor(pa_sink_input *i, const char *key, const pa_cvolume *volume_factor); + 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_volume_ramp(pa_sink_input *i, const pa_cvolume_ramp *ramp, pa_bool_t send_msg, pa_bool_t save); ++void pa_sink_input_set_volume_ramp(pa_sink_input *i, const pa_cvolume_ramp *ramp, bool send_msg); + + void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save); + +-- +1.9.1 + diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0005-sink-input-volume-Add-support-for-volume-ramp-factor.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0005-sink-input-volume-Add-support-for-volume-ramp-factor.patch new file mode 100644 index 000000000..e371b7ec5 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0005-sink-input-volume-Add-support-for-volume-ramp-factor.patch @@ -0,0 +1,299 @@ +From a98e78ccc4f12d6efad2832a09202651e2a8b6cd Mon Sep 17 00:00:00 2001 +From: Sangchul Lee +Date: Sat, 27 Aug 2016 21:33:19 +0900 +Subject: [PATCH 5/6] sink-input, volume: Add support for volume ramp factor + +Previously, using pa_sink_input_set_volume_ramp() is hard to manage +if there are several callers. These new volume ramp factor APIs make it +easy for caller to use and to set more than one volume ramp factor. +New volume ramp factor will be applied by the multiplication of the other +ramp factors that have been already set. + +APIs are added as below. + - pa_sink_input_add_volume_ramp_factor() + - pa_sink_input_remove_volume_ramp_factor() + - pa_cvolume_ramp_compatible() + - pa_sw_cvolume_ramp_multiply() + - pa_cvolume_ramp_valid() + +Signed-off-by: Sangchul Lee +--- + src/map-file | 3 ++ + src/pulse/volume.c | 44 ++++++++++++++++++ + src/pulse/volume.h | 9 ++++ + src/pulsecore/sink-input.c | 108 +++++++++++++++++++++++++++++++++++++++++++++ + src/pulsecore/sink-input.h | 5 +++ + 5 files changed, 169 insertions(+) + +diff --git a/src/map-file b/src/map-file +index ef9b57d..7577c14 100644 +--- a/src/map-file ++++ b/src/map-file +@@ -142,6 +142,8 @@ pa_cvolume_ramp_equal; + pa_cvolume_ramp_init; + pa_cvolume_ramp_set; + pa_cvolume_ramp_channel_ramp_set; ++pa_cvolume_ramp_compatible; ++pa_cvolume_ramp_valid; + pa_cvolume_remap; + pa_cvolume_scale; + pa_cvolume_scale_mask; +@@ -344,6 +346,7 @@ pa_sw_cvolume_divide_scalar; + pa_sw_cvolume_multiply; + pa_sw_cvolume_multiply_scalar; + pa_sw_cvolume_snprint_dB; ++pa_sw_cvolume_ramp_multiply; + pa_sw_volume_divide; + pa_sw_volume_from_dB; + pa_sw_volume_from_linear; +diff --git a/src/pulse/volume.c b/src/pulse/volume.c +index 85072c1..8d99150 100644 +--- a/src/pulse/volume.c ++++ b/src/pulse/volume.c +@@ -1049,3 +1049,47 @@ pa_cvolume_ramp* pa_cvolume_ramp_channel_ramp_set(pa_cvolume_ramp *ramp, unsigne + + return ramp; + } ++ ++int pa_cvolume_ramp_compatible(const pa_cvolume_ramp *ramp, const pa_sample_spec *ss) { ++ ++ pa_assert(ramp); ++ pa_assert(ss); ++ ++ pa_return_val_if_fail(pa_cvolume_ramp_valid(ramp), 0); ++ pa_return_val_if_fail(pa_sample_spec_valid(ss), 0); ++ ++ return ramp->channels == ss->channels; ++} ++ ++pa_cvolume_ramp *pa_sw_cvolume_ramp_multiply(pa_cvolume_ramp *dest, const pa_cvolume_ramp *a, const pa_cvolume_ramp *b) { ++ unsigned i; ++ ++ pa_assert(dest); ++ pa_assert(a); ++ pa_assert(b); ++ ++ pa_return_val_if_fail(pa_cvolume_ramp_valid(a), NULL); ++ pa_return_val_if_fail(pa_cvolume_ramp_valid(b), NULL); ++ ++ for (i = 0; i < a->channels && i < b->channels; i++) ++ dest->ramps[i].target = pa_sw_volume_multiply(a->ramps[i].target, b->ramps[i].target); ++ ++ dest->channels = (uint8_t) i; ++ ++ return dest; ++} ++ ++int pa_cvolume_ramp_valid(const pa_cvolume_ramp *ramp) { ++ unsigned c; ++ ++ pa_assert(ramp); ++ ++ if (!pa_channels_valid(ramp->channels)) ++ return 0; ++ ++ for (c = 0; c < ramp->channels; c++) ++ if (!PA_VOLUME_IS_VALID(ramp->ramps[c].target)) ++ return 0; ++ ++ return 1; ++} +diff --git a/src/pulse/volume.h b/src/pulse/volume.h +index 2ae3451..65fcb08 100644 +--- a/src/pulse/volume.h ++++ b/src/pulse/volume.h +@@ -458,12 +458,21 @@ 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 the volume ramp of the first n channels to PA_VOLUME_NORM */ ++#define pa_cvolume_ramp_reset(a, n, t, l) pa_cvolume_ramp_set((a), (n), (t), (l), PA_VOLUME_NORM) ++ + /** 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); + ++int pa_cvolume_ramp_compatible(const pa_cvolume_ramp *ramp, const pa_sample_spec *ss); ++ ++int pa_cvolume_ramp_valid(const pa_cvolume_ramp *ramp); ++ ++pa_cvolume_ramp *pa_sw_cvolume_ramp_multiply(pa_cvolume_ramp *dest, const pa_cvolume_ramp *a, const pa_cvolume_ramp *b); ++ + PA_C_DECL_END + + #endif +diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c +index e1968e0..6f89aa1 100644 +--- a/src/pulsecore/sink-input.c ++++ b/src/pulsecore/sink-input.c +@@ -53,6 +53,11 @@ struct volume_factor_entry { + pa_cvolume volume; + }; + ++struct volume_ramp_factor_entry { ++ char *key; ++ pa_cvolume_ramp ramp; ++}; ++ + static struct volume_factor_entry *volume_factor_entry_new(const char *key, const pa_cvolume *volume) { + struct volume_factor_entry *entry; + +@@ -83,6 +88,37 @@ static void volume_factor_from_hashmap(pa_cvolume *v, pa_hashmap *items, uint8_t + pa_sw_cvolume_multiply(v, v, &entry->volume); + } + ++static struct volume_ramp_factor_entry *volume_ramp_factor_entry_new(const char *key, const pa_cvolume_ramp *ramp) { ++ struct volume_ramp_factor_entry *entry; ++ ++ pa_assert(key); ++ pa_assert(ramp); ++ ++ entry = pa_xnew(struct volume_ramp_factor_entry, 1); ++ entry->key = pa_xstrdup(key); ++ ++ entry->ramp = *ramp; ++ ++ return entry; ++} ++ ++static void volume_ramp_factor_entry_free(struct volume_ramp_factor_entry *ramp_entry) { ++ pa_assert(ramp_entry); ++ ++ pa_xfree(ramp_entry->key); ++ pa_xfree(ramp_entry); ++} ++ ++static void volume_ramp_factor_from_hashmap(pa_cvolume_ramp *r, pa_hashmap *items, uint8_t channels, pa_volume_ramp_type_t type, long length) { ++ struct volume_ramp_factor_entry *entry; ++ void *state = NULL; ++ ++ pa_cvolume_ramp_reset(r, channels, type, length); ++ PA_HASHMAP_FOREACH(entry, items, state) ++ pa_sw_cvolume_ramp_multiply(r, r, &entry->ramp); ++ ++} ++ + static void sink_input_free(pa_object *o); + static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v); + +@@ -500,6 +536,8 @@ int pa_sink_input_new( + i->volume_factor_sink_items = data->volume_factor_sink_items; + data->volume_factor_sink_items = NULL; + volume_factor_from_hashmap(&i->volume_factor_sink, i->volume_factor_sink_items, i->sink->sample_spec.channels); ++ i->ramp_factor_items = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, ++ (pa_free_cb_t) volume_ramp_factor_entry_free); + + i->real_ratio = i->reference_ratio = data->volume; + pa_cvolume_reset(&i->soft_volume, i->sample_spec.channels); +@@ -764,6 +802,9 @@ static void sink_input_free(pa_object *o) { + if (i->volume_factor_sink_items) + pa_hashmap_free(i->volume_factor_sink_items); + ++ if (i->ramp_factor_items) ++ pa_hashmap_free(i->ramp_factor_items); ++ + pa_xfree(i->driver); + pa_xfree(i); + } +@@ -1367,6 +1408,73 @@ int pa_sink_input_remove_volume_factor(pa_sink_input *i, const char *key) { + return 0; + } + ++void pa_sink_input_add_volume_ramp_factor(pa_sink_input *i, const char *key, const pa_cvolume_ramp *ramp_factor, bool send_msg) { ++ struct volume_ramp_factor_entry *r; ++ ++ pa_sink_input_assert_ref(i); ++ pa_assert_ctl_context(); ++ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); ++ pa_assert(ramp_factor); ++ pa_assert(key); ++ pa_assert(pa_cvolume_ramp_valid(ramp_factor)); ++ pa_assert(ramp_factor->channels == 1 || pa_cvolume_ramp_compatible(ramp_factor, &i->sample_spec)); ++ ++ r = volume_ramp_factor_entry_new(key, ramp_factor); ++ if (!pa_cvolume_ramp_compatible(ramp_factor, &i->sample_spec)) ++ pa_cvolume_ramp_set(&r->ramp, i->sample_spec.channels, ramp_factor->ramps[0].type, ramp_factor->ramps[0].length, ramp_factor->ramps[0].target); ++ ++ pa_assert_se(pa_hashmap_put(i->ramp_factor_items, r->key, r) >= 0); ++ if (pa_hashmap_size(i->ramp_factor_items) == 1) ++ pa_cvolume_ramp_set(&i->ramp_factor, i->sample_spec.channels, r->ramp.ramps[0].type, r->ramp.ramps[0].length, r->ramp.ramps[0].target); ++ else ++ pa_sw_cvolume_ramp_multiply(&i->ramp_factor, &i->ramp_factor, &r->ramp); ++ ++ pa_cvolume_ramp_convert(&i->ramp_factor, &i->ramp, i->sample_spec.rate); ++ ++ 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); ++ ++ return 0; ++} ++ ++/* Returns 0 if an entry was removed and -1 if no entry for the given key was ++ * found. */ ++int pa_sink_input_remove_volume_ramp_factor(pa_sink_input *i, const char *key, bool send_msg) { ++ struct volume_ramp_factor_entry *r; ++ ++ pa_sink_input_assert_ref(i); ++ pa_assert(key); ++ pa_assert_ctl_context(); ++ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); ++ ++ r = pa_hashmap_remove(i->ramp_factor_items, key); ++ if (!r) ++ return -1; ++ ++ switch (pa_hashmap_size(i->ramp_factor_items)) { ++ case 0: ++ pa_cvolume_ramp_reset(&i->ramp_factor, i->sample_spec.channels, r->ramp.ramps[0].type, r->ramp.ramps[0].length); ++ break; ++ case 1: { ++ struct volume_ramp_factor_entry *rf; ++ rf = pa_hashmap_first(i->ramp_factor_items); ++ pa_cvolume_ramp_set(&i->ramp_factor, i->sample_spec.channels, r->ramp.ramps[0].type, r->ramp.ramps[0].length, rf->ramp.ramps[0].target); ++ break; ++ } ++ default: ++ volume_ramp_factor_from_hashmap(&i->ramp_factor, i->ramp_factor_items, i->ramp_factor.channels, i->ramp_factor.ramps[0].type, i->ramp_factor.ramps[0].length); ++ } ++ ++ volume_ramp_factor_entry_free(r); ++ ++ pa_cvolume_ramp_convert(&i->ramp_factor, &i->ramp, i->sample_spec.rate); ++ ++ 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); ++ ++ return 0; ++} ++ + /* Called from main thread */ + void pa_sink_input_set_volume_ramp( + pa_sink_input *i, +diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h +index 92f61c3..5430d53 100644 +--- a/src/pulsecore/sink-input.h ++++ b/src/pulsecore/sink-input.h +@@ -113,6 +113,9 @@ struct pa_sink_input { + pa_cvolume volume_factor_sink; /* A second volume factor in format of the sink this stream is connected to. */ + pa_hashmap *volume_factor_sink_items; + ++ pa_cvolume_ramp ramp_factor; ++ pa_hashmap *ramp_factor_items; ++ + bool volume_writable:1; + + bool muted:1; +@@ -379,6 +382,8 @@ void pa_sink_input_add_volume_factor(pa_sink_input *i, const char *key, const pa + 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_volume_ramp(pa_sink_input *i, const pa_cvolume_ramp *ramp, bool send_msg); ++void pa_sink_input_add_volume_ramp_factor(pa_sink_input *i, const char *key, const pa_cvolume_ramp *ramp_factor, bool send_msg); ++int pa_sink_input_remove_volume_ramp_factor(pa_sink_input *i, const char *key, bool send_msg); + + void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save); + +-- +1.9.1 + diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0006-sink-input-Remove-pa_sink_input_set_volume_ramp.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0006-sink-input-Remove-pa_sink_input_set_volume_ramp.patch new file mode 100644 index 000000000..50110bd14 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/0006-sink-input-Remove-pa_sink_input_set_volume_ramp.patch @@ -0,0 +1,63 @@ +From ac9a99505fd768b66dd92e9091e80b576cabc00d Mon Sep 17 00:00:00 2001 +From: Sangchul Lee +Date: Sat, 27 Aug 2016 21:33:20 +0900 +Subject: [PATCH 6/6] sink-input: Remove pa_sink_input_set_volume_ramp() + +Please use pa_sink_input_add_volume_ramp_factor() or +pa_sink_input_remove_volume_ramp_factor() instead of it. + +Signed-off-by: Sangchul Lee +--- + src/pulsecore/sink-input.c | 23 ----------------------- + src/pulsecore/sink-input.h | 1 - + 2 files changed, 24 deletions(-) + +diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c +index 6f89aa1..4c7c568 100644 +--- a/src/pulsecore/sink-input.c ++++ b/src/pulsecore/sink-input.c +@@ -1475,29 +1475,6 @@ int pa_sink_input_remove_volume_ramp_factor(pa_sink_input *i, const char *key, b + 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) { +- 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); +diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h +index 5430d53..5fef3d5 100644 +--- a/src/pulsecore/sink-input.h ++++ b/src/pulsecore/sink-input.h +@@ -381,7 +381,6 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool s + void pa_sink_input_add_volume_factor(pa_sink_input *i, const char *key, const pa_cvolume *volume_factor); + 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_volume_ramp(pa_sink_input *i, const pa_cvolume_ramp *ramp, bool send_msg); + void pa_sink_input_add_volume_ramp_factor(pa_sink_input *i, const char *key, const pa_cvolume_ramp *ramp_factor, bool send_msg); + int pa_sink_input_remove_volume_ramp_factor(pa_sink_input *i, const char *key, bool send_msg); + +-- +1.9.1 + diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/enable-ofono-hfp-backend.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/enable-ofono-hfp-backend.patch new file mode 100644 index 000000000..85b1ae03d --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio-10.0/enable-ofono-hfp-backend.patch @@ -0,0 +1,11 @@ +--- pulseaudio-9.0/src/daemon/default.pa.in.orig 2017-05-17 17:26:20.415369638 +0000 ++++ pulseaudio-9.0/src/daemon/default.pa.in 2017-05-17 17:26:49.995316383 +0000 +@@ -82,7 +82,7 @@ + .endif + + .ifexists module-bluetooth-discover@PA_SOEXT@ +-load-module module-bluetooth-discover ++load-module module-bluetooth-discover headset=ofono + .endif + ])dnl + diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0008-install-files-for-a-module-development.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0008-install-files-for-a-module-development.patch new file mode 100644 index 000000000..ac164d643 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0008-install-files-for-a-module-development.patch @@ -0,0 +1,53 @@ +--- a/Makefile.am 2016-04-12 11:28:10.832997230 +0200 ++++ b/Makefile.am 2016-04-12 11:32:41.868996777 +0200 +@@ -49,7 +49,13 @@ + vala/libpulse-simple.deps vala/libpulse-simple.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 $(top_srcdir)/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 += \ +@@ -103,6 +109,9 @@ + check-daemon: + $(MAKE) -C src check-daemon + ++pulsecore-config.h: config.h ++ cp $< $@ ++ + .PHONY: homepage distcleancheck doxygen + + # see git-version-gen +--- a/configure.ac 2016-04-12 11:34:10.605996629 +0200 ++++ b/configure.ac 2016-04-12 11:37:43.502996274 +0200 +@@ -1464,6 +1464,7 @@ + man/default.pa.5.xml + man/pulse-cli-syntax.5.xml + man/start-pulseaudio-x11.1.xml ++pulseaudio-module-devel.pc + ]) + + AC_CONFIG_FILES([src/esdcompat:src/daemon/esdcompat.in], [chmod +x src/esdcompat]) +--- /dev/null 2016-03-15 16:08:23.302999643 +0100 ++++ b/pulseaudio-module-devel.pc.in 2016-03-17 16:16:58.427977484 +0100 +@@ -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/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0010-volume-ramp-additions-to-the-low-level-infra.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0010-volume-ramp-additions-to-the-low-level-infra.patch new file mode 100644 index 000000000..e249bc438 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0010-volume-ramp-additions-to-the-low-level-infra.patch @@ -0,0 +1,503 @@ +--- a/src/map-file 2016-04-12 15:03:17.009975690 +0200 ++++ b/src/map-file 2016-04-12 15:03:52.389975631 +0200 +@@ -136,6 +136,9 @@ + pa_cvolume_merge; + pa_cvolume_min; + pa_cvolume_min_mask; ++pa_cvolume_ramp_init; ++pa_cvolume_ramp_set; ++pa_cvolume_ramp_channel_ramp_set; + pa_cvolume_remap; + pa_cvolume_scale; + pa_cvolume_scale_mask; +--- a/src/pulse/def.h 2016-04-12 15:05:58.245975421 +0200 ++++ b/src/pulse/def.h 2016-04-12 15:13:19.424974685 +0200 +@@ -347,11 +347,15 @@ + * 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 */ +@@ -380,6 +384,7 @@ + #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 */ + +@@ -1047,6 +1052,13 @@ + /** \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 +--- a/src/pulse/volume.c 2016-04-12 15:13:38.598974653 +0200 ++++ b/src/pulse/volume.c 2016-04-12 15:27:57.729973219 +0200 +@@ -445,7 +445,10 @@ + 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++) +@@ -986,3 +989,51 @@ + + return pa_cvolume_scale(v, m); + } ++ ++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; ++} +--- a/src/pulse/volume.h 2016-04-12 15:40:34.989971955 +0200 ++++ b/src/pulse/volume.h 2016-04-12 15:38:50.708972129 +0200 +@@ -413,6 +413,36 @@ + * 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 pam_cvolume_ramp { ++ uint8_t channels; ++ pa_volume_ramp_t ramps[PA_CHANNELS_MAX]; ++} pa_cvolume_ramp; ++ ++/** 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 +--- a/src/pulsecore/sample-util.c 2016-04-12 15:41:51.812971827 +0200 ++++ b/src/pulsecore/sample-util.c 2016-04-12 16:31:56.795966812 +0200 +@@ -41,6 +41,13 @@ + + #define PA_SILENCE_MAX (PA_PAGE_SIZE*16) + ++#define VOLUME_PADDING 32 ++ ++typedef union { ++ float f; ++ uint32_t i; ++} volume_val; ++ + pa_memblock *pa_silence_memblock(pa_memblock* b, const pa_sample_spec *spec) { + void *data; + +@@ -403,3 +410,292 @@ + usec = pa_bytes_to_usec_round_up(size, from); + return pa_usec_to_bytes_round_up(usec, to); + } ++ ++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; ++ float temp; ++ ++ for (i = 0; i < dst->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; ++ } ++ ++ 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; ++} +--- a/src/pulsecore/sample-util.h 2016-04-12 15:53:06.327970701 +0200 ++++ b/src/pulsecore/sample-util.h 2016-04-12 16:24:16.356967580 +0200 +@@ -45,6 +45,33 @@ + + pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length); + ++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); ++ + size_t pa_frame_align(size_t l, const pa_sample_spec *ss) PA_GCC_PURE; + + bool pa_frame_aligned(size_t l, const pa_sample_spec *ss) PA_GCC_PURE; diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0011-volume-ramp-adding-volume-ramping-to-sink-input.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0011-volume-ramp-adding-volume-ramping-to-sink-input.patch new file mode 100644 index 000000000..08fb79248 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0011-volume-ramp-adding-volume-ramping-to-sink-input.patch @@ -0,0 +1,159 @@ +--- a/src/pulsecore/sink-input.c 2016-04-12 16:50:41.311964935 +0200 ++++ b/src/pulsecore/sink-input.c 2016-04-12 17:22:40.420961732 +0200 +@@ -525,6 +525,11 @@ + 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->sample_spec.channels); ++ else ++ pa_cvolume_ramp_int_init(&i->ramp, PA_VOLUME_NORM, data->sample_spec.channels); ++ + i->thread_info.state = i->state; + i->thread_info.attached = false; + pa_atomic_store(&i->thread_info.drained, 1); +@@ -541,6 +546,8 @@ + 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); + +@@ -922,6 +929,8 @@ + 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); +@@ -958,6 +967,16 @@ + 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; +@@ -974,6 +993,16 @@ + 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(&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, &rchunk); + pa_memblock_unref(rchunk.memblock); + } +@@ -1338,6 +1367,31 @@ + 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); +@@ -1929,6 +1983,12 @@ + } + 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; ++ return 0; ++ + case PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE: + if (i->thread_info.muted != i->muted) { + i->thread_info.muted = i->muted; +--- a/src/pulsecore/sink-input.h 2016-04-12 16:50:46.712964926 +0200 ++++ b/src/pulsecore/sink-input.h 2016-04-12 17:30:24.289960958 +0200 +@@ -59,7 +59,8 @@ + 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 { +@@ -122,6 +123,9 @@ + * 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 +@@ -250,6 +254,8 @@ + pa_usec_t requested_sink_latency; + + pa_hashmap *direct_outputs; ++ ++ pa_cvolume_ramp_int ramp; + } thread_info; + + void *userdata; +@@ -266,6 +272,7 @@ + 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 + }; + +@@ -374,6 +381,8 @@ + + void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save); + ++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/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0012-volume-ramp-add-volume-ramping-to-sink.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0012-volume-ramp-add-volume-ramping-to-sink.patch new file mode 100644 index 000000000..43e8283ef --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0012-volume-ramp-add-volume-ramping-to-sink.patch @@ -0,0 +1,159 @@ +--- a/src/pulsecore/sink.c 2016-04-12 18:01:23.403957855 +0200 ++++ b/src/pulsecore/sink.c 2016-04-12 18:44:49.677953506 +0200 +@@ -324,6 +324,8 @@ + &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); +@@ -347,6 +349,8 @@ + 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); + +@@ -1182,6 +1186,7 @@ + + } else if (n == 1) { + pa_cvolume volume; ++ pa_cvolume target; + + *result = info[0].chunk; + pa_memblock_ref(result->memblock); +@@ -1198,9 +1203,20 @@ + 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; +@@ -1290,6 +1306,7 @@ + + } else { + void *ptr; ++ pa_cvolume target_vol; + + ptr = pa_memblock_acquire(target->memblock); + +@@ -1299,6 +1316,15 @@ + &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); + } + +@@ -2058,6 +2084,32 @@ + 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) { +@@ -2713,6 +2765,12 @@ + 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; ++ return 0; ++ + case PA_SINK_MESSAGE_GET_VOLUME: + + if ((s->flags & PA_SINK_DEFERRED_VOLUME) && s->get_volume) { +--- a/src/pulsecore/sink.h 2016-04-12 18:01:42.117957824 +0200 ++++ b/src/pulsecore/sink.h 2016-04-12 18:23:29.394955642 +0200 +@@ -105,6 +105,9 @@ + pa_cvolume saved_volume; + bool saved_save_volume:1; + ++ /* for volume ramps */ ++ pa_cvolume_ramp_int ramp; ++ + pa_asyncmsgq *asyncmsgq; + + pa_memchunk silence; +@@ -300,6 +303,8 @@ + 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; +@@ -333,6 +338,7 @@ + PA_SINK_MESSAGE_SET_MAX_REQUEST, + PA_SINK_MESSAGE_SET_PORT, + PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE, ++ PA_SINK_MESSAGE_SET_VOLUME_RAMP, + PA_SINK_MESSAGE_SET_LATENCY_OFFSET, + PA_SINK_MESSAGE_MAX + } pa_sink_message_t; +@@ -453,6 +459,8 @@ + void pa_sink_set_mute(pa_sink *sink, bool mute, bool save); + bool pa_sink_get_mute(pa_sink *sink, bool force_refresh); + ++void pa_sink_set_volume_ramp(pa_sink *s, const pa_cvolume_ramp *ramp, bool send_msg, bool save); ++ + bool pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p); + + int pa_sink_set_port(pa_sink *s, const char *name, bool save); diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0013-add-internal-corking-state-for-sink-input.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0013-add-internal-corking-state-for-sink-input.patch new file mode 100644 index 000000000..cb68e26af --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0013-add-internal-corking-state-for-sink-input.patch @@ -0,0 +1,72 @@ +--- a/src/pulsecore/sink-input.c 2016-04-13 11:44:04.389999429 +0200 ++++ b/src/pulsecore/sink-input.c 2016-04-13 11:52:45.167998560 +0200 +@@ -786,6 +786,9 @@ + 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); +@@ -1493,13 +1496,38 @@ + } + } + ++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 */ +--- a/src/pulsecore/sink-input.h 2016-04-13 11:44:28.035999390 +0200 ++++ b/src/pulsecore/sink-input.h 2016-04-13 11:53:56.325998441 +0200 +@@ -126,6 +126,9 @@ + /* 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 +@@ -357,6 +360,7 @@ + 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/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0020-core-util-Add-pa_join.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0020-core-util-Add-pa_join.patch new file mode 100644 index 000000000..891a5b83a --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0020-core-util-Add-pa_join.patch @@ -0,0 +1,37 @@ +--- a/src/pulsecore/core-util.c 2016-04-13 16:17:33.314016929 +0200 ++++ b/src/pulsecore/core-util.c 2016-04-13 16:18:31.186016833 +0200 +@@ -1104,6 +1104,24 @@ + 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() */ +--- a/src/pulsecore/core-util.h 2016-04-13 16:17:39.177016919 +0200 ++++ b/src/pulsecore/core-util.h 2016-04-13 16:19:09.141016769 +0200 +@@ -108,6 +108,7 @@ + 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/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0021-dynarray-Add-pa_dynarray_get_raw_array.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0021-dynarray-Add-pa_dynarray_get_raw_array.patch new file mode 100644 index 000000000..040913c8f --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0021-dynarray-Add-pa_dynarray_get_raw_array.patch @@ -0,0 +1,21 @@ +--- a/src/pulsecore/dynarray.c 2016-04-13 16:25:38.628016119 +0200 ++++ b/src/pulsecore/dynarray.c 2016-04-13 16:26:54.694015992 +0200 +@@ -91,3 +91,9 @@ + + return array->n_entries; + } ++ ++void * const *pa_dynarray_get_raw_array(pa_dynarray *array) { ++ pa_assert(array); ++ ++ return array->data; ++} +--- a/src/pulsecore/dynarray.h 2016-04-13 16:25:43.225016112 +0200 ++++ b/src/pulsecore/dynarray.h 2016-04-13 16:27:47.414015904 +0200 +@@ -52,5 +52,5 @@ + 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/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0022-device-port-Add-pa_device_port.active.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0022-device-port-Add-pa_device_port.active.patch new file mode 100644 index 000000000..4a8143f61 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0022-device-port-Add-pa_device_port.active.patch @@ -0,0 +1,198 @@ +--- a/src/pulsecore/core.h 2016-04-13 16:38:56.392014788 +0200 ++++ b/src/pulsecore/core.h 2016-04-13 16:41:50.331014498 +0200 +@@ -125,6 +125,7 @@ + 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; +--- a/src/pulsecore/device-port.c 2016-04-13 16:39:37.411014719 +0200 ++++ b/src/pulsecore/device-port.c 2016-04-13 17:06:47.393011999 +0200 +@@ -175,6 +175,21 @@ + 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); ++} ++ + pa_device_port *pa_device_port_find_best(pa_hashmap *ports) + { + void *state; +--- a/src/pulsecore/device-port.h 2016-04-13 16:39:51.603014696 +0200 ++++ b/src/pulsecore/device-port.h 2016-04-13 17:07:12.649011957 +0200 +@@ -46,6 +46,7 @@ + + 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 */ +--- a/src/pulsecore/sink.c 2016-04-13 16:40:11.131014663 +0200 ++++ b/src/pulsecore/sink.c 2016-04-13 17:14:30.963011225 +0200 +@@ -658,6 +658,9 @@ + 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); +@@ -685,6 +688,9 @@ + 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); +@@ -3297,6 +3303,7 @@ + /* 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); +@@ -3313,11 +3320,15 @@ + 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); +@@ -3326,17 +3337,26 @@ + 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); + +--- a/src/pulsecore/source.c 2016-04-13 16:40:25.290014640 +0200 ++++ b/src/pulsecore/source.c 2016-04-13 17:21:26.051010533 +0200 +@@ -603,6 +603,9 @@ + 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); + } +@@ -623,6 +626,9 @@ + 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); +@@ -2576,6 +2582,7 @@ + /* 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); +@@ -2592,11 +2599,15 @@ + 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); +@@ -2605,16 +2616,26 @@ + 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/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0030-volume-api-Add-libvolume-api.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0030-volume-api-Add-libvolume-api.patch new file mode 100644 index 000000000..b904ac315 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0030-volume-api-Add-libvolume-api.patch @@ -0,0 +1,6118 @@ +From 0223ac3e092249c792bedcab2bf5dbaec443f960 Mon Sep 17 00:00:00 2001 +From: Tanu Kaskinen +Date: Wed, 21 May 2014 11:51:27 +0300 +Subject: [PATCH 1/1] volume-api: Add libvolume-api.so + +This library implements the "core" of the new volume system, which +will be used by several modules. + +Change-Id: Ib25ada1392e83237a3908e6064ee0ad6dff7afaf +Signed-off-by: Jaska Uimonen +--- + Makefile.am | 3 + + src/Makefile.am | 19 +- + src/map-file | 15 + + src/modules/volume-api/audio-group.c | 448 +++++++++++++ + src/modules/volume-api/audio-group.h | 85 +++ + src/modules/volume-api/binding.c | 386 +++++++++++ + src/modules/volume-api/binding.h | 128 ++++ + src/modules/volume-api/bvolume.h | 43 ++ + src/modules/volume-api/device-creator.c | 1108 +++++++++++++++++++++++++++++++ + src/modules/volume-api/device-creator.h | 32 + + src/modules/volume-api/device.c | 293 ++++++++ + src/modules/volume-api/device.h | 76 +++ + src/modules/volume-api/mute-control.c | 306 +++++++++ + src/modules/volume-api/mute-control.h | 102 +++ + src/modules/volume-api/sstream.c | 366 ++++++++++ + src/modules/volume-api/sstream.h | 108 +++ + src/modules/volume-api/stream-creator.c | 691 +++++++++++++++++++ + src/modules/volume-api/stream-creator.h | 32 + + src/modules/volume-api/volume-api.c | 647 ++++++++++++++++++ + src/modules/volume-api/volume-api.h | 163 +++++ + src/modules/volume-api/volume-control.c | 363 ++++++++++ + src/modules/volume-api/volume-control.h | 112 ++++ + src/pulse/ext-volume-api.c | 275 ++++++++ + src/pulse/ext-volume-api.h | 68 ++ + 24 files changed, 5868 insertions(+), 1 deletion(-) + create mode 100644 src/modules/volume-api/audio-group.c + create mode 100644 src/modules/volume-api/audio-group.h + create mode 100644 src/modules/volume-api/binding.c + create mode 100644 src/modules/volume-api/binding.h + create mode 100644 src/modules/volume-api/bvolume.h + create mode 100644 src/modules/volume-api/device-creator.c + create mode 100644 src/modules/volume-api/device-creator.h + create mode 100644 src/modules/volume-api/device.c + create mode 100644 src/modules/volume-api/device.h + create mode 100644 src/modules/volume-api/mute-control.c + create mode 100644 src/modules/volume-api/mute-control.h + create mode 100644 src/modules/volume-api/sstream.c + create mode 100644 src/modules/volume-api/sstream.h + create mode 100644 src/modules/volume-api/stream-creator.c + create mode 100644 src/modules/volume-api/stream-creator.h + create mode 100644 src/modules/volume-api/volume-api.c + create mode 100644 src/modules/volume-api/volume-api.h + create mode 100644 src/modules/volume-api/volume-control.c + create mode 100644 src/modules/volume-api/volume-control.h + create mode 100644 src/pulse/ext-volume-api.c + create mode 100644 src/pulse/ext-volume-api.h + +diff --git a/Makefile.am b/Makefile.am +index 9431d4a..cf4a648 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -57,6 +57,9 @@ + moduledevinternal_DATA = src/pulse/internal.h src/pulse/client-conf.h src/pulse/fork-detect.h + moduledevinternaldir = $(includedir)/pulsemodule/pulse + ++moduledevvolumeapi_DATA = $(top_srcdir)/src/modules/volume-api/*.h ++moduledevvolumeapidir = $(includedir)/pulsemodule/modules/volume-api ++ + if HAVE_GLIB20 + pkgconfig_DATA += \ + libpulse-mainloop-glib.pc +diff --git a/src/Makefile.am b/src/Makefile.am +index 22b9b81..bc41dca 100644 +--- a/src/Makefile.am 2016-04-13 15:51:34.439019531 +0200 ++++ b/src/Makefile.am 2016-04-13 15:53:03.721019382 +0200 +@@ -792,6 +792,7 @@ + pulse/ext-device-manager.h \ + pulse/ext-device-restore.h \ + pulse/ext-stream-restore.h \ ++ pulse/ext-volume-api.h \ + pulse/format.h \ + pulse/gccmacro.h \ + pulse/introspect.h \ +@@ -838,6 +839,7 @@ + pulse/ext-device-manager.c pulse/ext-device-manager.h \ + pulse/ext-device-restore.c pulse/ext-device-restore.h \ + pulse/ext-stream-restore.c pulse/ext-stream-restore.h \ ++ pulse/ext-volume-api.c pulse/ext-volume-api.h \ + pulse/format.c pulse/format.h \ + pulse/gccmacro.h \ + pulse/internal.h \ +@@ -1018,7 +1020,8 @@ modlibexec_LTLIBRARIES = \ + libprotocol-cli.la \ + libprotocol-simple.la \ + libprotocol-http.la \ +- libprotocol-native.la ++ libprotocol-native.la \ ++ libvolume-api.la + + if HAVE_WEBRTC + modlibexec_LTLIBRARIES += libwebrtc-util.la +@@ -1065,6 +1068,20 @@ libprotocol_native_la_CFLAGS += $(DBUS_CFLAGS) + libprotocol_native_la_LIBADD += $(DBUS_LIBS) + endif + ++libvolume_api_la_SOURCES = \ ++ modules/volume-api/audio-group.c modules/volume-api/audio-group.h \ ++ modules/volume-api/binding.c modules/volume-api/binding.h \ ++ modules/volume-api/bvolume.h \ ++ modules/volume-api/device.c modules/volume-api/device.h \ ++ modules/volume-api/device-creator.c modules/volume-api/device-creator.h \ ++ modules/volume-api/mute-control.c modules/volume-api/mute-control.h \ ++ modules/volume-api/sstream.c modules/volume-api/sstream.h \ ++ modules/volume-api/stream-creator.c modules/volume-api/stream-creator.h \ ++ modules/volume-api/volume-api.c modules/volume-api/volume-api.h \ ++ modules/volume-api/volume-control.c modules/volume-api/volume-control.h ++libvolume_api_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version ++libvolume_api_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la ++ + if HAVE_ESOUND + libprotocol_esound_la_SOURCES = pulsecore/protocol-esound.c pulsecore/protocol-esound.h pulsecore/esound.h + libprotocol_esound_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version +diff --git a/src/map-file b/src/map-file +index fbf3f22..1f64a2f 100644 +--- a/src/map-file ++++ b/src/map-file +@@ -172,6 +172,21 @@ + pa_ext_stream_restore_subscribe; + pa_ext_stream_restore_test; + pa_ext_stream_restore_write; ++pa_ext_volume_api_balance_valid; ++pa_ext_volume_api_bvolume_copy_balance; ++pa_ext_volume_api_bvolume_get_left_right_balance; ++pa_ext_volume_api_bvolume_get_rear_front_balance; ++pa_ext_volume_api_bvolume_equal; ++pa_ext_volume_api_bvolume_from_cvolume; ++pa_ext_volume_api_bvolume_init_invalid; ++pa_ext_volume_api_bvolume_init_mono; ++pa_ext_volume_api_bvolume_remap; ++pa_ext_volume_api_bvolume_reset_balance; ++pa_ext_volume_api_bvolume_set_left_right_balance; ++pa_ext_volume_api_bvolume_set_rear_front_balance; ++pa_ext_volume_api_bvolume_snprint_balance; ++pa_ext_volume_api_bvolume_to_cvolume; ++pa_ext_volume_api_bvolume_valid; + pa_format_info_copy; + pa_format_info_free; + pa_format_info_from_string; +diff --git a/src/modules/volume-api/audio-group.c b/src/modules/volume-api/audio-group.c +new file mode 100644 +index 0000000..76bfa69 +--- /dev/null ++++ b/src/modules/volume-api/audio-group.c +@@ -0,0 +1,448 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "audio-group.h" ++ ++#include ++ ++#include ++ ++int pa_audio_group_new(pa_volume_api *api, const char *name, const char *description, pa_audio_group **group) { ++ pa_audio_group *group_local; ++ int r; ++ ++ pa_assert(api); ++ pa_assert(name); ++ pa_assert(description); ++ pa_assert(group); ++ ++ group_local = pa_xnew0(pa_audio_group, 1); ++ group_local->volume_api = api; ++ group_local->index = pa_volume_api_allocate_audio_group_index(api); ++ ++ r = pa_volume_api_register_name(api, name, true, &group_local->name); ++ if (r < 0) ++ goto fail; ++ ++ group_local->description = pa_xstrdup(description); ++ group_local->proplist = pa_proplist_new(); ++ group_local->volume_streams = pa_hashmap_new(NULL, NULL); ++ group_local->mute_streams = pa_hashmap_new(NULL, NULL); ++ ++ *group = group_local; ++ ++ return 0; ++ ++fail: ++ pa_audio_group_free(group_local); ++ ++ return r; ++} ++ ++void pa_audio_group_put(pa_audio_group *group) { ++ const char *prop_key; ++ void *state = NULL; ++ ++ pa_assert(group); ++ ++ pa_volume_api_add_audio_group(group->volume_api, group); ++ ++ group->linked = true; ++ ++ pa_log_debug("Created audio group #%u.", group->index); ++ pa_log_debug(" Name: %s", group->name); ++ pa_log_debug(" Description: %s", group->description); ++ pa_log_debug(" Volume control: %s", group->volume_control ? group->volume_control->name : "(unset)"); ++ pa_log_debug(" Mute control: %s", group->mute_control ? group->mute_control->name : "(unset)"); ++ pa_log_debug(" Properties:"); ++ ++ while ((prop_key = pa_proplist_iterate(group->proplist, &state))) ++ pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(group->proplist, prop_key))); ++ ++ pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_PUT], group); ++} ++ ++void pa_audio_group_unlink(pa_audio_group *group) { ++ pas_stream *stream; ++ ++ pa_assert(group); ++ ++ if (group->unlinked) { ++ pa_log_debug("Unlinking audio group %s (already unlinked, this is a no-op).", group->name); ++ return; ++ } ++ ++ group->unlinked = true; ++ ++ pa_log_debug("Unlinking audio group %s.", group->name); ++ ++ if (group->linked) ++ pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK], group); ++ ++ pa_volume_api_remove_audio_group(group->volume_api, group); ++ ++ while ((stream = pa_hashmap_first(group->mute_streams))) ++ pas_stream_set_audio_group_for_mute(stream, NULL); ++ ++ while ((stream = pa_hashmap_first(group->volume_streams))) ++ pas_stream_set_audio_group_for_volume(stream, NULL); ++ ++ if (group->mute_control_binding) { ++ pa_binding_free(group->mute_control_binding); ++ group->mute_control_binding = NULL; ++ } ++ ++ if (group->volume_control_binding) { ++ pa_binding_free(group->volume_control_binding); ++ group->volume_control_binding = NULL; ++ } ++ ++ pa_audio_group_set_have_own_mute_control(group, false); ++ pa_audio_group_set_have_own_volume_control(group, false); ++ ++ if (group->mute_control) { ++ pa_mute_control_remove_audio_group(group->mute_control, group); ++ group->mute_control = NULL; ++ } ++ ++ if (group->volume_control) { ++ pa_volume_control_remove_audio_group(group->volume_control, group); ++ group->volume_control = NULL; ++ } ++} ++ ++void pa_audio_group_free(pa_audio_group *group) { ++ pa_assert(group); ++ ++ if (!group->unlinked) ++ pa_audio_group_unlink(group); ++ ++ if (group->mute_streams) ++ pa_hashmap_free(group->mute_streams); ++ ++ if (group->volume_streams) ++ pa_hashmap_free(group->volume_streams); ++ ++ if (group->proplist) ++ pa_proplist_free(group->proplist); ++ ++ pa_xfree(group->description); ++ ++ if (group->name) ++ pa_volume_api_unregister_name(group->volume_api, group->name); ++ ++ pa_xfree(group); ++} ++ ++const char *pa_audio_group_get_name(pa_audio_group *group) { ++ pa_assert(group); ++ ++ return group->name; ++} ++ ++static int volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, ++ bool set_balance) { ++ pa_audio_group *group; ++ pas_stream *stream; ++ void *state; ++ ++ pa_assert(control); ++ pa_assert(volume); ++ ++ group = control->userdata; ++ ++ PA_HASHMAP_FOREACH(stream, group->volume_streams, state) { ++ if (stream->own_volume_control) ++ pa_volume_control_set_volume(stream->own_volume_control, volume, set_volume, set_balance); ++ } ++ ++ return 0; ++} ++ ++static void volume_control_set_initial_volume_cb(pa_volume_control *control) { ++ pa_audio_group *group; ++ pas_stream *stream; ++ void *state; ++ ++ pa_assert(control); ++ ++ group = control->userdata; ++ ++ PA_HASHMAP_FOREACH(stream, group->volume_streams, state) { ++ if (stream->own_volume_control) ++ pa_volume_control_set_volume(stream->own_volume_control, &control->volume, true, true); ++ } ++} ++ ++void pa_audio_group_set_have_own_volume_control(pa_audio_group *group, bool have) { ++ pa_assert(group); ++ ++ if (have == group->have_own_volume_control) ++ return; ++ ++ if (have) { ++ pa_bvolume initial_volume; ++ ++ if (group->volume_api->core->flat_volumes) ++ /* Usually the initial volume should get overridden by some module ++ * that manages audio group volume levels, but if there's no such ++ * module, let's try to avoid too high volume in flat volume ++ * mode. */ ++ pa_bvolume_init_mono(&initial_volume, 0.3 * PA_VOLUME_NORM); ++ else ++ pa_bvolume_init_mono(&initial_volume, PA_VOLUME_NORM); ++ ++ pa_assert(!group->own_volume_control); ++ group->own_volume_control = pa_volume_control_new(group->volume_api, "audio-group-volume-control", ++ group->description, false, false); ++ pa_volume_control_set_owner_audio_group(group->own_volume_control, group); ++ group->own_volume_control->set_volume = volume_control_set_volume_cb; ++ group->own_volume_control->userdata = group; ++ pa_volume_control_put(group->own_volume_control, &initial_volume, volume_control_set_initial_volume_cb); ++ } else { ++ pa_volume_control_free(group->own_volume_control); ++ group->own_volume_control = NULL; ++ } ++ ++ group->have_own_volume_control = have; ++} ++ ++static int mute_control_set_mute_cb(pa_mute_control *control, bool mute) { ++ pa_audio_group *group; ++ pas_stream *stream; ++ void *state; ++ ++ pa_assert(control); ++ ++ group = control->userdata; ++ ++ PA_HASHMAP_FOREACH(stream, group->mute_streams, state) { ++ if (stream->own_mute_control) ++ pa_mute_control_set_mute(stream->own_mute_control, mute); ++ } ++ ++ return 0; ++} ++ ++static void mute_control_set_initial_mute_cb(pa_mute_control *control) { ++ pa_audio_group *group; ++ pas_stream *stream; ++ void *state; ++ ++ pa_assert(control); ++ ++ group = control->userdata; ++ ++ PA_HASHMAP_FOREACH(stream, group->mute_streams, state) { ++ if (stream->own_mute_control) ++ pa_mute_control_set_mute(stream->own_mute_control, control->mute); ++ } ++} ++ ++void pa_audio_group_set_have_own_mute_control(pa_audio_group *group, bool have) { ++ pa_assert(group); ++ ++ if (have == group->have_own_mute_control) ++ return; ++ ++ group->have_own_mute_control = have; ++ ++ if (have) { ++ pa_assert(!group->own_mute_control); ++ group->own_mute_control = pa_mute_control_new(group->volume_api, "audio-group-mute-control", group->description); ++ pa_mute_control_set_owner_audio_group(group->own_mute_control, group); ++ group->own_mute_control->set_mute = mute_control_set_mute_cb; ++ group->own_mute_control->userdata = group; ++ pa_mute_control_put(group->own_mute_control, false, true, mute_control_set_initial_mute_cb); ++ } else { ++ pa_mute_control_free(group->own_mute_control); ++ group->own_mute_control = NULL; ++ } ++} ++ ++static void set_volume_control_internal(pa_audio_group *group, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(group); ++ ++ old_control = group->volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_volume_control_remove_audio_group(old_control, group); ++ ++ group->volume_control = control; ++ ++ if (control) ++ pa_volume_control_add_audio_group(control, group); ++ ++ if (!group->linked || group->unlinked) ++ return; ++ ++ pa_log_debug("The volume control of audio group %s changed from %s to %s.", group->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_VOLUME_CONTROL_CHANGED], group); ++} ++ ++void pa_audio_group_set_volume_control(pa_audio_group *group, pa_volume_control *control) { ++ pa_assert(group); ++ ++ if (group->volume_control_binding) { ++ pa_binding_free(group->volume_control_binding); ++ group->volume_control_binding = NULL; ++ } ++ ++ set_volume_control_internal(group, control); ++} ++ ++static void set_mute_control_internal(pa_audio_group *group, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(group); ++ ++ old_control = group->mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_mute_control_remove_audio_group(old_control, group); ++ ++ group->mute_control = control; ++ ++ if (control) ++ pa_mute_control_add_audio_group(control, group); ++ ++ if (!group->linked || group->unlinked) ++ return; ++ ++ pa_log_debug("The mute control of audio group %s changed from %s to %s.", group->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_MUTE_CONTROL_CHANGED], group); ++} ++ ++void pa_audio_group_set_mute_control(pa_audio_group *group, pa_mute_control *control) { ++ pa_assert(group); ++ ++ if (group->mute_control_binding) { ++ pa_binding_free(group->mute_control_binding); ++ group->mute_control_binding = NULL; ++ } ++ ++ set_mute_control_internal(group, control); ++} ++ ++void pa_audio_group_bind_volume_control(pa_audio_group *group, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = group, ++ .set_value = (pa_binding_set_value_cb_t) set_volume_control_internal, ++ }; ++ ++ pa_assert(group); ++ pa_assert(target_info); ++ ++ if (group->volume_control_binding) ++ pa_binding_free(group->volume_control_binding); ++ ++ group->volume_control_binding = pa_binding_new(group->volume_api, &owner_info, target_info); ++} ++ ++void pa_audio_group_bind_mute_control(pa_audio_group *group, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = group, ++ .set_value = (pa_binding_set_value_cb_t) set_mute_control_internal, ++ }; ++ ++ pa_assert(group); ++ pa_assert(target_info); ++ ++ if (group->mute_control_binding) ++ pa_binding_free(group->mute_control_binding); ++ ++ group->mute_control_binding = pa_binding_new(group->volume_api, &owner_info, target_info); ++} ++ ++void pa_audio_group_add_volume_stream(pa_audio_group *group, pas_stream *stream) { ++ pa_assert(group); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_put(group->volume_streams, stream, stream) >= 0); ++ ++ if (stream->own_volume_control && group->own_volume_control) ++ pa_volume_control_set_volume(stream->own_volume_control, &group->own_volume_control->volume, true, true); ++ ++ pa_log_debug("Stream %s added to audio group %s (volume).", stream->name, group->name); ++} ++ ++void pa_audio_group_remove_volume_stream(pa_audio_group *group, pas_stream *stream) { ++ pa_assert(group); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_remove(group->volume_streams, stream)); ++ ++ pa_log_debug("Stream %s removed from audio group %s (volume).", stream->name, group->name); ++} ++ ++void pa_audio_group_add_mute_stream(pa_audio_group *group, pas_stream *stream) { ++ pa_assert(group); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_put(group->mute_streams, stream, stream) >= 0); ++ ++ if (stream->own_mute_control && group->own_mute_control) ++ pa_mute_control_set_mute(stream->own_mute_control, group->own_mute_control->mute); ++ ++ pa_log_debug("Stream %s added to audio group %s (mute).", stream->name, group->name); ++} ++ ++void pa_audio_group_remove_mute_stream(pa_audio_group *group, pas_stream *stream) { ++ pa_assert(group); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_remove(group->mute_streams, stream)); ++ ++ pa_log_debug("Stream %s removed from audio group %s (mute).", stream->name, group->name); ++} ++ ++pa_binding_target_type *pa_audio_group_create_binding_target_type(pa_volume_api *api) { ++ pa_binding_target_type *type; ++ ++ pa_assert(api); ++ ++ type = pa_binding_target_type_new(PA_AUDIO_GROUP_BINDING_TARGET_TYPE, api->audio_groups, ++ &api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_PUT], ++ &api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK], ++ (pa_binding_target_type_get_name_cb_t) pa_audio_group_get_name); ++ pa_binding_target_type_add_field(type, PA_AUDIO_GROUP_BINDING_TARGET_FIELD_VOLUME_CONTROL, ++ PA_BINDING_CALCULATE_FIELD_OFFSET(pa_audio_group, volume_control)); ++ pa_binding_target_type_add_field(type, PA_AUDIO_GROUP_BINDING_TARGET_FIELD_MUTE_CONTROL, ++ PA_BINDING_CALCULATE_FIELD_OFFSET(pa_audio_group, mute_control)); ++ ++ return type; ++} +diff --git a/src/modules/volume-api/audio-group.h b/src/modules/volume-api/audio-group.h +new file mode 100644 +index 0000000..41591ba +--- /dev/null ++++ b/src/modules/volume-api/audio-group.h +@@ -0,0 +1,85 @@ ++#ifndef fooaudiogrouphfoo ++#define fooaudiogrouphfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++typedef struct pa_audio_group pa_audio_group; ++ ++#define PA_AUDIO_GROUP_BINDING_TARGET_TYPE "AudioGroup" ++#define PA_AUDIO_GROUP_BINDING_TARGET_FIELD_VOLUME_CONTROL "volume_control" ++#define PA_AUDIO_GROUP_BINDING_TARGET_FIELD_MUTE_CONTROL "mute_control" ++ ++struct pa_audio_group { ++ pa_volume_api *volume_api; ++ uint32_t index; ++ const char *name; ++ char *description; ++ pa_proplist *proplist; ++ pa_volume_control *volume_control; ++ pa_mute_control *mute_control; ++ bool have_own_volume_control; ++ bool have_own_mute_control; ++ pa_volume_control *own_volume_control; ++ pa_mute_control *own_mute_control; ++ ++ pa_binding *volume_control_binding; ++ pa_binding *mute_control_binding; ++ pa_hashmap *volume_streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ ++ pa_hashmap *mute_streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ ++ ++ bool linked; ++ bool unlinked; ++}; ++ ++int pa_audio_group_new(pa_volume_api *api, const char *name, const char *description, pa_audio_group **group); ++void pa_audio_group_put(pa_audio_group *group); ++void pa_audio_group_unlink(pa_audio_group *group); ++void pa_audio_group_free(pa_audio_group *group); ++ ++const char *pa_audio_group_get_name(pa_audio_group *group); ++ ++/* Called by policy modules. */ ++void pa_audio_group_set_have_own_volume_control(pa_audio_group *group, bool have); ++void pa_audio_group_set_have_own_mute_control(pa_audio_group *group, bool have); ++void pa_audio_group_set_volume_control(pa_audio_group *group, pa_volume_control *control); ++void pa_audio_group_set_mute_control(pa_audio_group *group, pa_mute_control *control); ++void pa_audio_group_bind_volume_control(pa_audio_group *group, pa_binding_target_info *target_info); ++void pa_audio_group_bind_mute_control(pa_audio_group *group, pa_binding_target_info *target_info); ++ ++/* Called from sstream.c only. */ ++void pa_audio_group_add_volume_stream(pa_audio_group *group, pas_stream *stream); ++void pa_audio_group_remove_volume_stream(pa_audio_group *group, pas_stream *stream); ++void pa_audio_group_add_mute_stream(pa_audio_group *group, pas_stream *stream); ++void pa_audio_group_remove_mute_stream(pa_audio_group *group, pas_stream *stream); ++ ++/* Called from volume-api.c only. */ ++pa_binding_target_type *pa_audio_group_create_binding_target_type(pa_volume_api *api); ++ ++#endif +diff --git a/src/modules/volume-api/binding.c b/src/modules/volume-api/binding.c +new file mode 100644 +index 0000000..6e73119 +--- /dev/null ++++ b/src/modules/volume-api/binding.c +@@ -0,0 +1,386 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "binding.h" ++ ++#include ++#include ++ ++#include ++#include ++ ++struct field_entry { ++ char *name; ++ size_t offset; ++}; ++ ++static void set_target_type(pa_binding *binding, pa_binding_target_type *type); ++static void set_target_object(pa_binding *binding, void *object); ++ ++pa_binding_owner_info *pa_binding_owner_info_new(pa_binding_set_value_cb_t set_value, void *userdata) { ++ pa_binding_owner_info *info; ++ ++ pa_assert(set_value); ++ ++ info = pa_xnew0(pa_binding_owner_info, 1); ++ info->set_value = set_value; ++ info->userdata = userdata; ++ ++ return info; ++} ++ ++pa_binding_owner_info *pa_binding_owner_info_copy(const pa_binding_owner_info *info) { ++ pa_assert(info); ++ ++ return pa_binding_owner_info_new(info->set_value, info->userdata); ++} ++ ++void pa_binding_owner_info_free(pa_binding_owner_info *info) { ++ pa_assert(info); ++ ++ pa_xfree(info); ++} ++ ++pa_binding_target_info *pa_binding_target_info_new(const char *type, const char *name, const char *field) { ++ pa_binding_target_info *info; ++ ++ pa_assert(type); ++ pa_assert(name); ++ pa_assert(field); ++ ++ info = pa_xnew0(pa_binding_target_info, 1); ++ info->type = pa_xstrdup(type); ++ info->name = pa_xstrdup(name); ++ info->field = pa_xstrdup(field); ++ ++ return info; ++} ++ ++int pa_binding_target_info_new_from_string(const char *str, const char *field, pa_binding_target_info **info) { ++ const char *colon; ++ char *type = NULL; ++ char *name = NULL; ++ ++ pa_assert(str); ++ pa_assert(field); ++ pa_assert(info); ++ ++ if (!pa_startswith(str, "bind:")) ++ goto fail; ++ ++ colon = strchr(str + 5, ':'); ++ if (!colon) ++ goto fail; ++ ++ type = pa_xstrndup(str + 5, colon - (str + 5)); ++ ++ if (!*type) ++ goto fail; ++ ++ name = pa_xstrdup(colon + 1); ++ ++ if (!*name) ++ goto fail; ++ ++ *info = pa_binding_target_info_new(type, name, field); ++ pa_xfree(name); ++ pa_xfree(type); ++ ++ return 0; ++ ++fail: ++ pa_log("Invalid binding target: %s", str); ++ pa_xfree(name); ++ pa_xfree(type); ++ ++ return -PA_ERR_INVALID; ++} ++ ++pa_binding_target_info *pa_binding_target_info_copy(const pa_binding_target_info *info) { ++ pa_assert(info); ++ ++ return pa_binding_target_info_new(info->type, info->name, info->field); ++} ++ ++void pa_binding_target_info_free(pa_binding_target_info *info) { ++ pa_assert(info); ++ ++ pa_xfree(info->field); ++ pa_xfree(info->name); ++ pa_xfree(info->type); ++ pa_xfree(info); ++} ++ ++static void field_entry_free(struct field_entry *entry) { ++ pa_assert(entry); ++ ++ pa_xfree(entry->name); ++ pa_xfree(entry); ++} ++ ++pa_binding_target_type *pa_binding_target_type_new(const char *name, pa_hashmap *objects, pa_hook *put_hook, ++ pa_hook *unlink_hook, pa_binding_target_type_get_name_cb_t get_name) { ++ pa_binding_target_type *type; ++ ++ pa_assert(name); ++ pa_assert(objects); ++ pa_assert(put_hook); ++ pa_assert(unlink_hook); ++ pa_assert(get_name); ++ ++ type = pa_xnew0(pa_binding_target_type, 1); ++ type->name = pa_xstrdup(name); ++ type->objects = objects; ++ type->put_hook = put_hook; ++ type->unlink_hook = unlink_hook; ++ type->get_name = get_name; ++ type->fields = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) field_entry_free); ++ ++ return type; ++} ++ ++void pa_binding_target_type_free(pa_binding_target_type *type) { ++ pa_assert(type); ++ ++ if (type->fields) ++ pa_hashmap_free(type->fields); ++ ++ pa_xfree(type->name); ++ pa_xfree(type); ++} ++ ++void pa_binding_target_type_add_field(pa_binding_target_type *type, const char *name, size_t offset) { ++ struct field_entry *entry; ++ ++ pa_assert(type); ++ pa_assert(name); ++ ++ entry = pa_xnew0(struct field_entry, 1); ++ entry->name = pa_xstrdup(name); ++ entry->offset = offset; ++ ++ pa_assert_se(pa_hashmap_put(type->fields, entry->name, entry) >= 0); ++} ++ ++int pa_binding_target_type_get_field_offset(pa_binding_target_type *type, const char *field, size_t *offset) { ++ struct field_entry *entry; ++ ++ pa_assert(type); ++ pa_assert(field); ++ pa_assert(offset); ++ ++ entry = pa_hashmap_get(type->fields, field); ++ if (!entry) ++ return -PA_ERR_NOENTITY; ++ ++ *offset = entry->offset; ++ ++ return 0; ++} ++ ++static pa_hook_result_t target_type_added_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_binding_target_type *type = call_data; ++ pa_binding *binding = userdata; ++ ++ pa_assert(type); ++ pa_assert(binding); ++ ++ if (!pa_streq(type->name, binding->target_info->type)) ++ return PA_HOOK_OK; ++ ++ set_target_type(binding, type); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t target_type_removed_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_binding_target_type *type = call_data; ++ pa_binding *binding = userdata; ++ ++ pa_assert(type); ++ pa_assert(binding); ++ ++ if (type != binding->target_type) ++ return PA_HOOK_OK; ++ ++ set_target_type(binding, NULL); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t target_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_binding *binding = userdata; ++ ++ pa_assert(call_data); ++ pa_assert(binding); ++ ++ if (!pa_streq(binding->target_type->get_name(call_data), binding->target_info->name)) ++ return PA_HOOK_OK; ++ ++ set_target_object(binding, call_data); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t target_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_binding *binding = userdata; ++ ++ pa_assert(call_data); ++ pa_assert(binding); ++ ++ if (call_data != binding->target_object) ++ return PA_HOOK_OK; ++ ++ set_target_object(binding, NULL); ++ ++ return PA_HOOK_OK; ++} ++ ++static void set_target_object(pa_binding *binding, void *object) { ++ pa_assert(binding); ++ ++ binding->target_object = object; ++ ++ if (object) { ++ if (binding->target_put_slot) { ++ pa_hook_slot_free(binding->target_put_slot); ++ binding->target_put_slot = NULL; ++ } ++ ++ if (!binding->target_unlink_slot) ++ binding->target_unlink_slot = pa_hook_connect(binding->target_type->unlink_hook, PA_HOOK_NORMAL, target_unlink_cb, ++ binding); ++ ++ if (binding->target_field_offset_valid) ++ binding->owner_info->set_value(binding->owner_info->userdata, ++ *((void **) (((uint8_t *) object) + binding->target_field_offset))); ++ else ++ binding->owner_info->set_value(binding->owner_info->userdata, NULL); ++ } else { ++ if (binding->target_unlink_slot) { ++ pa_hook_slot_free(binding->target_unlink_slot); ++ binding->target_unlink_slot = NULL; ++ } ++ ++ if (binding->target_type) { ++ if (!binding->target_put_slot) ++ binding->target_put_slot = pa_hook_connect(binding->target_type->put_hook, PA_HOOK_NORMAL, target_put_cb, binding); ++ } else { ++ if (binding->target_put_slot) { ++ pa_hook_slot_free(binding->target_put_slot); ++ binding->target_put_slot = NULL; ++ } ++ } ++ ++ binding->owner_info->set_value(binding->owner_info->userdata, NULL); ++ } ++} ++ ++static void set_target_type(pa_binding *binding, pa_binding_target_type *type) { ++ pa_assert(binding); ++ ++ binding->target_type = type; ++ ++ if (type) { ++ int r; ++ ++ if (binding->target_type_added_slot) { ++ pa_hook_slot_free(binding->target_type_added_slot); ++ binding->target_type_added_slot = NULL; ++ } ++ ++ if (!binding->target_type_removed_slot) ++ binding->target_type_removed_slot = ++ pa_hook_connect(&binding->volume_api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_REMOVED], ++ PA_HOOK_NORMAL, target_type_removed_cb, binding); ++ ++ r = pa_binding_target_type_get_field_offset(type, binding->target_info->field, &binding->target_field_offset); ++ if (r >= 0) ++ binding->target_field_offset_valid = true; ++ else { ++ pa_log_warn("Reference to non-existing field \"%s\" in binding target type \"%s\".", binding->target_info->field, ++ type->name); ++ binding->target_field_offset_valid = false; ++ } ++ ++ set_target_object(binding, pa_hashmap_get(type->objects, binding->target_info->name)); ++ } else { ++ if (binding->target_type_removed_slot) { ++ pa_hook_slot_free(binding->target_type_removed_slot); ++ binding->target_type_removed_slot = NULL; ++ } ++ ++ if (!binding->target_type_added_slot) ++ binding->target_type_added_slot = ++ pa_hook_connect(&binding->volume_api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_ADDED], ++ PA_HOOK_NORMAL, target_type_added_cb, binding); ++ ++ binding->target_field_offset_valid = false; ++ ++ set_target_object(binding, NULL); ++ } ++} ++ ++pa_binding *pa_binding_new(pa_volume_api *api, const pa_binding_owner_info *owner_info, ++ const pa_binding_target_info *target_info) { ++ pa_binding *binding; ++ ++ pa_assert(api); ++ pa_assert(owner_info); ++ pa_assert(target_info); ++ ++ binding = pa_xnew0(pa_binding, 1); ++ binding->volume_api = api; ++ binding->owner_info = pa_binding_owner_info_copy(owner_info); ++ binding->target_info = pa_binding_target_info_copy(target_info); ++ ++ set_target_type(binding, pa_hashmap_get(api->binding_target_types, target_info->type)); ++ ++ return binding; ++} ++ ++void pa_binding_free(pa_binding *binding) { ++ pa_assert(binding); ++ ++ if (binding->target_unlink_slot) ++ pa_hook_slot_free(binding->target_unlink_slot); ++ ++ if (binding->target_put_slot) ++ pa_hook_slot_free(binding->target_put_slot); ++ ++ if (binding->target_type_removed_slot) ++ pa_hook_slot_free(binding->target_type_removed_slot); ++ ++ if (binding->target_type_added_slot) ++ pa_hook_slot_free(binding->target_type_added_slot); ++ ++ if (binding->target_info) ++ pa_binding_target_info_free(binding->target_info); ++ ++ if (binding->owner_info) ++ pa_binding_owner_info_free(binding->owner_info); ++ ++ pa_xfree(binding); ++} +diff --git a/src/modules/volume-api/binding.h b/src/modules/volume-api/binding.h +new file mode 100644 +index 0000000..ba4dea8 +--- /dev/null ++++ b/src/modules/volume-api/binding.h +@@ -0,0 +1,128 @@ ++#ifndef foobindinghfoo ++#define foobindinghfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++ ++typedef struct pa_binding pa_binding; ++typedef struct pa_binding_owner_info pa_binding_owner_info; ++typedef struct pa_binding_target_info pa_binding_target_info; ++typedef struct pa_binding_target_type pa_binding_target_type; ++ ++typedef void (*pa_binding_set_value_cb_t)(void *userdata, void *value); ++ ++struct pa_binding_owner_info { ++ /* This is the object that has the variable that the binding is created ++ * for. */ ++ void *userdata; ++ ++ /* Called when the owner object's value needs to be updated. The userdata ++ * parameter of the callback is the same as the userdata field in this ++ * struct, and the value parameter is the new value for whatever variable ++ * the binding was created for. */ ++ pa_binding_set_value_cb_t set_value; ++}; ++ ++pa_binding_owner_info *pa_binding_owner_info_new(pa_binding_set_value_cb_t set_value, void *userdata); ++pa_binding_owner_info *pa_binding_owner_info_copy(const pa_binding_owner_info *info); ++void pa_binding_owner_info_free(pa_binding_owner_info *info); ++ ++struct pa_binding_target_info { ++ /* The target type name as registered with ++ * pa_binding_target_type_register(). */ ++ char *type; ++ ++ /* The target object name as returned by the get_name callback of ++ * pa_binding_target_type. */ ++ char *name; ++ ++ /* The target field of the target object. */ ++ char *field; ++}; ++ ++pa_binding_target_info *pa_binding_target_info_new(const char *type, const char *name, const char *field); ++ ++/* The string format is "bind:TYPE:NAME". */ ++int pa_binding_target_info_new_from_string(const char *str, const char *field, pa_binding_target_info **info); ++ ++pa_binding_target_info *pa_binding_target_info_copy(const pa_binding_target_info *info); ++void pa_binding_target_info_free(pa_binding_target_info *info); ++ ++typedef const char *(*pa_binding_target_type_get_name_cb_t)(void *object); ++ ++struct pa_binding_target_type { ++ /* Identifier for this target type. */ ++ char *name; ++ ++ /* name -> object. Points directly to some "master" object hashmap, so the ++ * hashmap is not owned by pa_binding_target_type. */ ++ pa_hashmap *objects; ++ ++ /* The hook that notifies of new objects if this target type. The call data ++ * of the hook must be a pointer to the new object (this should be true for ++ * all PUT hooks, so don't worry too much). */ ++ pa_hook *put_hook; ++ ++ /* The hook that notifies of unlinked objects of this target type. The call ++ * data of the hook must be a pointer to the removed object (this should be ++ * true for all UNLINK hooks, so don't worry too much). */ ++ pa_hook *unlink_hook; ++ ++ /* Function for getting the name of an object of this target type. */ ++ pa_binding_target_type_get_name_cb_t get_name; ++ ++ pa_hashmap *fields; ++}; ++ ++pa_binding_target_type *pa_binding_target_type_new(const char *name, pa_hashmap *objects, pa_hook *put_hook, ++ pa_hook *unlink_hook, pa_binding_target_type_get_name_cb_t get_name); ++void pa_binding_target_type_free(pa_binding_target_type *type); ++ ++/* Useful when calling pa_binding_target_type_add_field(). */ ++#define PA_BINDING_CALCULATE_FIELD_OFFSET(type, field) ((size_t) &(((type *) 0)->field)) ++ ++/* Called during the type initialization (right after ++ * pa_binding_target_type_new()). */ ++void pa_binding_target_type_add_field(pa_binding_target_type *type, const char *name, size_t offset); ++ ++int pa_binding_target_type_get_field_offset(pa_binding_target_type *type, const char *field, size_t *offset); ++ ++struct pa_binding { ++ pa_volume_api *volume_api; ++ pa_binding_owner_info *owner_info; ++ pa_binding_target_info *target_info; ++ pa_binding_target_type *target_type; ++ void *target_object; ++ size_t target_field_offset; ++ bool target_field_offset_valid; ++ pa_hook_slot *target_type_added_slot; ++ pa_hook_slot *target_type_removed_slot; ++ pa_hook_slot *target_put_slot; ++ pa_hook_slot *target_unlink_slot; ++}; ++ ++pa_binding *pa_binding_new(pa_volume_api *api, const pa_binding_owner_info *owner_info, ++ const pa_binding_target_info *target_info); ++void pa_binding_free(pa_binding *binding); ++ ++#endif +diff --git a/src/modules/volume-api/bvolume.h b/src/modules/volume-api/bvolume.h +new file mode 100644 +index 0000000..0317fb6 +--- /dev/null ++++ b/src/modules/volume-api/bvolume.h +@@ -0,0 +1,43 @@ ++#ifndef foobvolumehfoo ++#define foobvolumehfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++ ++typedef pa_ext_volume_api_bvolume pa_bvolume; ++ ++#define pa_balance_valid pa_ext_volume_api_balance_valid ++#define pa_bvolume_valid pa_ext_volume_api_bvolume_valid ++#define pa_bvolume_init_invalid pa_ext_volume_api_bvolume_init_invalid ++#define pa_bvolume_init_mono pa_ext_volume_api_bvolume_init_mono ++#define pa_bvolume_equal pa_ext_volume_api_bvolume_equal ++#define pa_bvolume_from_cvolume pa_ext_volume_api_bvolume_from_cvolume ++#define pa_bvolume_to_cvolume pa_ext_volume_api_bvolume_to_cvolume ++#define pa_bvolume_copy_balance pa_ext_volume_api_bvolume_copy_balance ++#define pa_bvolume_reset_balance pa_ext_volume_api_bvolume_reset_balance ++#define pa_bvolume_remap pa_ext_volume_api_bvolume_remap ++ ++#define PA_BVOLUME_SNPRINT_BALANCE_MAX PA_EXT_VOLUME_API_BVOLUME_SNPRINT_BALANCE_MAX ++#define pa_bvolume_snprint_balance pa_ext_volume_api_bvolume_snprint_balance ++ ++#endif +diff --git a/src/modules/volume-api/device-creator.c b/src/modules/volume-api/device-creator.c +new file mode 100644 +index 0000000..1d912ba +--- /dev/null ++++ b/src/modules/volume-api/device-creator.c +@@ -0,0 +1,1108 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "device-creator.h" ++ ++#include ++#include ++#include ++ ++#include ++#include ++ ++struct pa_device_creator { ++ pa_volume_api *volume_api; ++ pa_hashmap *devices; /* pa_device_port/pa_sink/pa_source -> struct device */ ++ pa_hook_slot *card_put_slot; ++ pa_hook_slot *card_unlink_slot; ++ pa_hook_slot *sink_put_slot; ++ pa_hook_slot *sink_unlink_slot; ++ pa_hook_slot *source_put_slot; ++ pa_hook_slot *source_unlink_slot; ++}; ++ ++enum device_type { ++ DEVICE_TYPE_PORT, ++ DEVICE_TYPE_PORT_MONITOR, ++ DEVICE_TYPE_SINK, ++ DEVICE_TYPE_SOURCE, ++}; ++ ++struct device_volume_control { ++ struct device *device; ++ pa_volume_control *volume_control; ++ ++ bool unlinked; ++ ++ pa_hook_slot *volume_changed_slot; ++}; ++ ++struct device_mute_control { ++ struct device *device; ++ pa_mute_control *mute_control; ++ ++ bool unlinked; ++ ++ pa_hook_slot *mute_changed_slot; ++}; ++ ++struct device { ++ pa_device_creator *creator; ++ enum device_type type; ++ pa_device_port *port; ++ pa_sink *sink; ++ pa_source *source; ++ pa_device *device; ++ struct device_volume_control *volume_control; ++ struct device_mute_control *mute_control; ++ ++ bool unlinked; ++ ++ pa_hook_slot *proplist_changed_slot; ++ pa_hook_slot *port_active_changed_slot; ++ struct device *monitor; ++}; ++ ++static const char *device_type_from_icon_name(const char *icon_name) { ++ if (!icon_name) ++ return NULL; ++ ++ if (pa_streq(icon_name, "audio-input-microphone")) ++ return "microphone"; ++ ++ if (pa_streq(icon_name, "audio-speakers")) ++ return "speakers"; ++ ++ if (pa_streq(icon_name, "audio-headphones")) ++ return "headphones"; ++ ++ return NULL; ++} ++ ++static const char *device_type_from_port_name(pa_device_port *port) { ++ pa_assert(port); ++ ++ if (strstr(port->name, "analog")) { ++ if (port->direction == PA_DIRECTION_INPUT) ++ return "analog-input"; ++ else ++ return "analog-output"; ++ } ++ ++ if (strstr(port->name, "hdmi")) { ++ if (port->direction == PA_DIRECTION_INPUT) ++ return "hdmi-input"; ++ else ++ return "hdmi-output"; ++ } ++ ++ if (strstr(port->name, "iec958")) { ++ if (port->direction == PA_DIRECTION_INPUT) ++ return "spdif-input"; ++ else ++ return "spdif-output"; ++ } ++ ++ return NULL; ++} ++ ++static const char *device_type_from_port(pa_device_port *port) { ++ const char *device_type; ++ ++ pa_assert(port); ++ ++ device_type = device_type_from_icon_name(pa_proplist_gets(port->proplist, PA_PROP_DEVICE_ICON_NAME)); ++ if (device_type) ++ return device_type; ++ ++ device_type = device_type_from_port_name(port); ++ if (device_type) ++ return device_type; ++ ++ return NULL; ++} ++ ++static const char *get_sink_description(pa_sink *sink) { ++ const char *description; ++ ++ pa_assert(sink); ++ ++ description = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION); ++ if (description) ++ return description; ++ ++ return sink->name; ++} ++ ++static const char *get_source_description(pa_source *source) { ++ const char *description; ++ ++ pa_assert(source); ++ ++ description = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION); ++ if (description) ++ return description; ++ ++ return source->name; ++} ++ ++static int volume_control_set_volume_cb(pa_volume_control *c, const pa_bvolume *volume, bool set_volume, bool set_balance) { ++ struct device_volume_control *control; ++ struct device *device; ++ pa_bvolume bvolume; ++ pa_cvolume cvolume; ++ ++ pa_assert(c); ++ pa_assert(volume); ++ ++ control = c->userdata; ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ pa_bvolume_from_cvolume(&bvolume, &device->sink->reference_volume, &device->sink->channel_map); ++ else ++ pa_bvolume_from_cvolume(&bvolume, &device->source->reference_volume, &device->source->channel_map); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ pa_bvolume_from_cvolume(&bvolume, &device->sink->reference_volume, &device->sink->channel_map); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ pa_bvolume_from_cvolume(&bvolume, &device->source->reference_volume, &device->source->channel_map); ++ break; ++ } ++ ++ if (set_volume) ++ bvolume.volume = volume->volume; ++ ++ if (set_balance) ++ pa_bvolume_copy_balance(&bvolume, volume); ++ ++ pa_bvolume_to_cvolume(&bvolume, &cvolume); ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ pa_sink_set_volume(device->sink, &cvolume, true, true); ++ else ++ pa_source_set_volume(device->source, &cvolume, true, true); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ pa_source_set_volume(device->source, &cvolume, true, true); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ pa_sink_set_volume(device->sink, &cvolume, true, true); ++ break; ++ } ++ ++ return 0; ++} ++ ++static struct device_volume_control *device_volume_control_new(struct device *device) { ++ struct device_volume_control *control; ++ const char *name = NULL; ++ bool convertible_to_dB = false; ++ bool channel_map_is_writable; ++ ++ pa_assert(device); ++ ++ control = pa_xnew0(struct device_volume_control, 1); ++ control->device = device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ name = "port-volume-control"; ++ ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ convertible_to_dB = device->sink->flags & PA_SINK_DECIBEL_VOLUME; ++ else ++ convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME; ++ ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ name = "port-monitor-volume-control"; ++ convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ name = "sink-volume-control"; ++ convertible_to_dB = device->sink->flags & PA_SINK_DECIBEL_VOLUME; ++ break; ++ ++ case DEVICE_TYPE_SOURCE: ++ name = "source-volume-control"; ++ convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME; ++ break; ++ } ++ ++ channel_map_is_writable = false; ++ control->volume_control = pa_volume_control_new(device->creator->volume_api, name, device->device->description, ++ convertible_to_dB, channel_map_is_writable); ++ control->volume_control->set_volume = volume_control_set_volume_cb; ++ control->volume_control->userdata = control; ++ ++ return control; ++} ++ ++static pa_hook_result_t sink_or_source_volume_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct device_volume_control *control = userdata; ++ struct device *device; ++ pa_sink *sink = NULL; ++ pa_source *source = NULL; ++ pa_bvolume bvolume; ++ ++ pa_assert(control); ++ pa_assert(call_data); ++ ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ sink = call_data; ++ else ++ source = call_data; ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ source = call_data; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ sink = call_data; ++ break; ++ } ++ ++ if ((sink && sink != device->sink) || (source && source != device->source)) ++ return PA_HOOK_OK; ++ ++ if (sink) ++ pa_bvolume_from_cvolume(&bvolume, &sink->reference_volume, &sink->channel_map); ++ else ++ pa_bvolume_from_cvolume(&bvolume, &source->reference_volume, &source->channel_map); ++ ++ pa_volume_control_volume_changed(control->volume_control, &bvolume, true, true); ++ ++ return PA_HOOK_OK; ++} ++ ++static void volume_control_set_initial_volume_cb(pa_volume_control *c) { ++ struct device_volume_control *control; ++ struct device *device; ++ pa_cvolume cvolume; ++ ++ pa_assert(c); ++ ++ control = c->userdata; ++ device = control->device; ++ pa_bvolume_to_cvolume(&control->volume_control->volume, &cvolume); ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ pa_sink_set_volume(device->sink, &cvolume, true, true); ++ else ++ pa_source_set_volume(device->source, &cvolume, true, true); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ pa_source_set_volume(device->source, &cvolume, true, true); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ pa_sink_set_volume(device->sink, &cvolume, true, true); ++ break; ++ } ++} ++ ++static void device_volume_control_put(struct device_volume_control *control) { ++ struct device *device; ++ pa_bvolume volume; ++ ++ pa_assert(control); ++ ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) { ++ control->volume_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); ++ pa_bvolume_from_cvolume(&volume, &device->sink->reference_volume, &device->sink->channel_map); ++ } else { ++ control->volume_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SOURCE_VOLUME_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); ++ pa_bvolume_from_cvolume(&volume, &device->source->reference_volume, &device->source->channel_map); ++ } ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ control->volume_changed_slot = pa_hook_connect(&device->source->core->hooks[PA_CORE_HOOK_SOURCE_VOLUME_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); ++ pa_bvolume_from_cvolume(&volume, &device->source->reference_volume, &device->source->channel_map); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ control->volume_changed_slot = pa_hook_connect(&device->sink->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); ++ pa_bvolume_from_cvolume(&volume, &device->sink->reference_volume, &device->sink->channel_map); ++ break; ++ } ++ ++ pa_volume_control_put(control->volume_control, &volume, volume_control_set_initial_volume_cb); ++} ++ ++static void device_volume_control_unlink(struct device_volume_control *control) { ++ pa_assert(control); ++ ++ if (control->unlinked) ++ return; ++ ++ control->unlinked = true; ++ ++ if (control->volume_control) ++ pa_volume_control_unlink(control->volume_control); ++ ++ if (control->volume_changed_slot) { ++ pa_hook_slot_free(control->volume_changed_slot); ++ control->volume_changed_slot = NULL; ++ } ++} ++ ++static void device_volume_control_free(struct device_volume_control *control) { ++ pa_assert(control); ++ ++ if (!control->unlinked) ++ device_volume_control_unlink(control); ++ ++ if (control->volume_control) ++ pa_volume_control_free(control->volume_control); ++ ++ pa_xfree(control); ++} ++ ++static int mute_control_set_mute_cb(pa_mute_control *c, bool mute) { ++ struct device_mute_control *control; ++ struct device *device; ++ ++ pa_assert(c); ++ ++ control = c->userdata; ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ pa_sink_set_mute(device->sink, mute, true); ++ else ++ pa_source_set_mute(device->source, mute, true); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ pa_source_set_mute(device->source, mute, true); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ pa_sink_set_mute(device->sink, mute, true); ++ break; ++ } ++ ++ return 0; ++} ++ ++static struct device_mute_control *device_mute_control_new(struct device *device) { ++ struct device_mute_control *control; ++ const char *name = NULL; ++ ++ pa_assert(device); ++ ++ control = pa_xnew0(struct device_mute_control, 1); ++ control->device = device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ name = "port-mute-control"; ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ name = "port-monitor-mute-control"; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ name = "sink-mute-control"; ++ break; ++ ++ case DEVICE_TYPE_SOURCE: ++ name = "source-mute-control"; ++ break; ++ } ++ ++ control->mute_control = pa_mute_control_new(device->creator->volume_api, name, device->device->description); ++ control->mute_control->set_mute = mute_control_set_mute_cb; ++ control->mute_control->userdata = control; ++ ++ return control; ++} ++ ++static pa_hook_result_t sink_or_source_mute_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct device_mute_control *control = userdata; ++ struct device *device; ++ pa_sink *sink = NULL; ++ pa_source *source = NULL; ++ bool mute; ++ ++ pa_assert(control); ++ pa_assert(call_data); ++ ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ sink = call_data; ++ else ++ source = call_data; ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ source = call_data; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ sink = call_data; ++ break; ++ } ++ ++ if ((sink && sink != device->sink) || (source && source != device->source)) ++ return PA_HOOK_OK; ++ ++ if (sink) ++ mute = sink->muted; ++ else ++ mute = source->muted; ++ ++ pa_mute_control_mute_changed(control->mute_control, mute); ++ ++ return PA_HOOK_OK; ++} ++ ++static void mute_control_set_initial_mute_cb(pa_mute_control *c) { ++ struct device_volume_control *control; ++ struct device *device; ++ ++ pa_assert(c); ++ ++ control = c->userdata; ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ pa_sink_set_mute(device->sink, c->mute, true); ++ else ++ pa_source_set_mute(device->source, c->mute, true); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ pa_source_set_mute(device->source, c->mute, true); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ pa_sink_set_mute(device->sink, c->mute, true); ++ break; ++ } ++} ++ ++static void device_mute_control_put(struct device_mute_control *control) { ++ struct device *device; ++ bool mute = false; ++ ++ pa_assert(control); ++ ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) { ++ control->mute_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SINK_MUTE_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); ++ mute = device->sink->muted; ++ } else { ++ control->mute_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SOURCE_MUTE_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); ++ mute = device->source->muted; ++ } ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ control->mute_changed_slot = pa_hook_connect(&device->source->core->hooks[PA_CORE_HOOK_SOURCE_MUTE_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); ++ mute = device->source->muted; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ control->mute_changed_slot = pa_hook_connect(&device->sink->core->hooks[PA_CORE_HOOK_SINK_MUTE_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); ++ mute = device->sink->muted; ++ break; ++ } ++ ++ pa_mute_control_put(control->mute_control, mute, true, mute_control_set_initial_mute_cb); ++} ++ ++static void device_mute_control_unlink(struct device_mute_control *control) { ++ pa_assert(control); ++ ++ if (control->unlinked) ++ return; ++ ++ control->unlinked = true; ++ ++ if (control->mute_control) ++ pa_mute_control_unlink(control->mute_control); ++ ++ if (control->mute_changed_slot) { ++ pa_hook_slot_free(control->mute_changed_slot); ++ control->mute_changed_slot = NULL; ++ } ++} ++ ++static void device_mute_control_free(struct device_mute_control *control) { ++ pa_assert(control); ++ ++ if (!control->unlinked) ++ device_mute_control_unlink(control); ++ ++ if (control->mute_control) ++ pa_mute_control_free(control->mute_control); ++ ++ pa_xfree(control); ++} ++ ++static void device_set_sink_and_source_from_port(struct device *device) { ++ pa_sink *sink; ++ pa_source *source; ++ uint32_t idx; ++ ++ pa_assert(device); ++ ++ device->sink = NULL; ++ device->source = NULL; ++ ++ if (!device->port->active) ++ return; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) { ++ PA_IDXSET_FOREACH(sink, device->port->card->sinks, idx) { ++ if (sink->active_port == device->port) { ++ device->sink = sink; ++ break; ++ } ++ } ++ ++ pa_assert(device->sink); ++ } else { ++ PA_IDXSET_FOREACH(source, device->port->card->sources, idx) { ++ if (source->active_port == device->port) { ++ device->source = source; ++ break; ++ } ++ } ++ ++ pa_assert(device->source); ++ } ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: { ++ PA_IDXSET_FOREACH(sink, device->port->card->sinks, idx) { ++ if (sink->active_port == device->port) { ++ device->sink = sink; ++ device->source = sink->monitor_source; ++ break; ++ } ++ } ++ ++ pa_assert(device->sink); ++ break; ++ } ++ ++ case DEVICE_TYPE_SINK: ++ case DEVICE_TYPE_SOURCE: ++ pa_assert_not_reached(); ++ } ++} ++ ++static pa_hook_result_t sink_or_source_proplist_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct device *device = userdata; ++ pa_sink *sink = NULL; ++ pa_source *source = NULL; ++ const char *description = NULL; ++ ++ pa_assert(device); ++ pa_assert(call_data); ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ case DEVICE_TYPE_PORT_MONITOR: ++ pa_assert_not_reached(); ++ ++ case DEVICE_TYPE_SINK: ++ sink = call_data; ++ ++ if (sink != device->sink) ++ return PA_HOOK_OK; ++ ++ description = get_sink_description(sink); ++ break; ++ ++ case DEVICE_TYPE_SOURCE: ++ source = call_data; ++ ++ if (source != device->source) ++ return PA_HOOK_OK; ++ ++ description = get_source_description(source); ++ break; ++ } ++ ++ pa_device_description_changed(device->device, description); ++ pa_volume_control_description_changed(device->volume_control->volume_control, description); ++ pa_mute_control_description_changed(device->mute_control->mute_control, description); ++ ++ return PA_HOOK_OK; ++} ++ ++static struct device *device_new(pa_device_creator *creator, enum device_type type, void *core_device) { ++ struct device *device = NULL; ++ const char *name = NULL; ++ char *description = NULL; ++ pa_direction_t direction = PA_DIRECTION_OUTPUT; ++ const char *device_type = NULL; ++ bool create_volume_and_mute_controls = true; ++ ++ pa_assert(creator); ++ pa_assert(core_device); ++ ++ device = pa_xnew0(struct device, 1); ++ device->creator = creator; ++ device->type = type; ++ ++ switch (type) { ++ case DEVICE_TYPE_PORT: ++ device->port = core_device; ++ device_set_sink_and_source_from_port(device); ++ name = "port-device"; ++ description = pa_xstrdup(device->port->description); ++ direction = device->port->direction; ++ device_type = device_type_from_port(device->port); ++ ++ if (!device->sink && !device->source) ++ create_volume_and_mute_controls = false; ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ device->port = core_device; ++ device_set_sink_and_source_from_port(device); ++ name = "port-monitor-device"; ++ description = pa_sprintf_malloc(_("Monitor of %s"), device->port->description); ++ direction = PA_DIRECTION_INPUT; ++ ++ if (!device->source) ++ create_volume_and_mute_controls = false; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ device->sink = core_device; ++ name = "sink-device"; ++ description = pa_xstrdup(get_sink_description(device->sink)); ++ direction = PA_DIRECTION_OUTPUT; ++ break; ++ ++ case DEVICE_TYPE_SOURCE: ++ device->source = core_device; ++ name = "source-device"; ++ description = pa_xstrdup(get_source_description(device->source)); ++ direction = PA_DIRECTION_INPUT; ++ break; ++ } ++ ++ device->device = pa_device_new(creator->volume_api, name, description, direction, &device_type, device_type ? 1 : 0); ++ pa_xfree(description); ++ ++ if (create_volume_and_mute_controls) { ++ device->volume_control = device_volume_control_new(device); ++ device->mute_control = device_mute_control_new(device); ++ } ++ ++ switch (type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ device->monitor = device_new(creator, DEVICE_TYPE_PORT_MONITOR, device->port); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ device->proplist_changed_slot = pa_hook_connect(&device->sink->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_proplist_changed_cb, device); ++ break; ++ ++ case DEVICE_TYPE_SOURCE: ++ device->proplist_changed_slot = pa_hook_connect(&device->source->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_proplist_changed_cb, device); ++ break; ++ } ++ ++ return device; ++} ++ ++static pa_hook_result_t port_active_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct device *device = userdata; ++ pa_device_port *port = call_data; ++ bool should_have_volume_and_mute_controls = false; ++ ++ pa_assert(device); ++ pa_assert(port); ++ ++ if (port != device->port) ++ return PA_HOOK_OK; ++ ++ device_set_sink_and_source_from_port(device); ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ should_have_volume_and_mute_controls = device->sink || device->source; ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ should_have_volume_and_mute_controls = !!device->source; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ case DEVICE_TYPE_SOURCE: ++ pa_assert_not_reached(); ++ } ++ ++ if (should_have_volume_and_mute_controls && !device->volume_control) { ++ pa_assert(!device->mute_control); ++ ++ device->volume_control = device_volume_control_new(device); ++ device_volume_control_put(device->volume_control); ++ pa_device_set_default_volume_control(device->device, device->volume_control->volume_control); ++ ++ device->mute_control = device_mute_control_new(device); ++ device_mute_control_put(device->mute_control); ++ pa_device_set_default_mute_control(device->device, device->mute_control->mute_control); ++ } ++ ++ if (!should_have_volume_and_mute_controls && device->volume_control) { ++ pa_assert(device->mute_control); ++ ++ device_mute_control_free(device->mute_control); ++ device->mute_control = NULL; ++ device_volume_control_free(device->volume_control); ++ device->volume_control = NULL; ++ } ++ ++ return PA_HOOK_OK; ++} ++ ++static void device_put(struct device *device) { ++ pa_assert(device); ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ case DEVICE_TYPE_PORT_MONITOR: ++ device->port_active_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_PORT_ACTIVE_CHANGED], ++ PA_HOOK_NORMAL, port_active_changed_cb, device); ++ ++ case DEVICE_TYPE_SINK: ++ case DEVICE_TYPE_SOURCE: ++ break; ++ } ++ ++ if (device->volume_control) ++ device_volume_control_put(device->volume_control); ++ ++ if (device->mute_control) ++ device_mute_control_put(device->mute_control); ++ ++ pa_device_put(device->device, device->volume_control ? device->volume_control->volume_control : NULL, ++ device->mute_control ? device->mute_control->mute_control : NULL); ++ ++ if (device->monitor) ++ device_put(device->monitor); ++} ++ ++static void device_unlink(struct device *device) { ++ pa_assert(device); ++ ++ if (device->unlinked) ++ return; ++ ++ device->unlinked = true; ++ ++ if (device->monitor) ++ device_unlink(device->monitor); ++ ++ if (device->device) ++ pa_device_unlink(device->device); ++ ++ if (device->mute_control) ++ device_mute_control_unlink(device->mute_control); ++ ++ if (device->volume_control) ++ device_volume_control_unlink(device->volume_control); ++ ++ if (device->port_active_changed_slot) { ++ pa_hook_slot_free(device->port_active_changed_slot); ++ device->port_active_changed_slot = NULL; ++ } ++} ++ ++static void device_free(struct device *device) { ++ pa_assert(device); ++ ++ if (!device->unlinked) ++ device_unlink(device); ++ ++ if (device->monitor) ++ device_free(device->monitor); ++ ++ if (device->proplist_changed_slot) ++ pa_hook_slot_free(device->proplist_changed_slot); ++ ++ if (device->mute_control) ++ device_mute_control_free(device->mute_control); ++ ++ if (device->volume_control) ++ device_volume_control_free(device->volume_control); ++ ++ if (device->device) ++ pa_device_free(device->device); ++ ++ pa_xfree(device); ++} ++ ++static void create_device(pa_device_creator *creator, enum device_type type, void *core_device) { ++ struct device *device; ++ ++ pa_assert(creator); ++ pa_assert(core_device); ++ ++ switch (type) { ++ case DEVICE_TYPE_PORT: ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ pa_assert_not_reached(); ++ ++ case DEVICE_TYPE_SINK: ++ if (!pa_hashmap_isempty(((pa_sink *) core_device)->ports)) ++ return; ++ break; ++ ++ case DEVICE_TYPE_SOURCE: { ++ pa_source *source = core_device; ++ ++ if (source->monitor_of && !pa_hashmap_isempty(source->monitor_of->ports)) ++ return; ++ ++ if (!pa_hashmap_isempty(((pa_source *) core_device)->ports)) ++ return; ++ break; ++ } ++ } ++ ++ device = device_new(creator, type, core_device); ++ pa_hashmap_put(creator->devices, core_device, device); ++ device_put(device); ++} ++ ++static pa_hook_result_t card_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_card *card = call_data; ++ pa_device_port *port; ++ void *state; ++ ++ pa_assert(creator); ++ pa_assert(card); ++ ++ PA_HASHMAP_FOREACH(port, card->ports, state) ++ create_device(creator, DEVICE_TYPE_PORT, port); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t card_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_card *card = call_data; ++ pa_device_port *port; ++ void *state; ++ ++ pa_assert(creator); ++ pa_assert(card); ++ ++ PA_HASHMAP_FOREACH(port, card->ports, state) ++ pa_hashmap_remove_and_free(creator->devices, port); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t sink_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_sink *sink = call_data; ++ ++ pa_assert(creator); ++ pa_assert(sink); ++ ++ create_device(creator, DEVICE_TYPE_SINK, sink); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t sink_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_sink *sink = call_data; ++ ++ pa_assert(creator); ++ pa_assert(sink); ++ ++ pa_hashmap_remove_and_free(creator->devices, sink); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t source_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_source *source = call_data; ++ ++ pa_assert(creator); ++ pa_assert(source); ++ ++ create_device(creator, DEVICE_TYPE_SOURCE, source); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t source_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_source *source = call_data; ++ ++ pa_assert(creator); ++ pa_assert(source); ++ ++ pa_hashmap_remove_and_free(creator->devices, source); ++ ++ return PA_HOOK_OK; ++} ++ ++pa_device_creator *pa_device_creator_new(pa_volume_api *api) { ++ pa_device_creator *creator; ++ pa_card *card; ++ uint32_t idx; ++ pa_sink *sink; ++ pa_source *source; ++ ++ pa_assert(api); ++ ++ creator = pa_xnew0(pa_device_creator, 1); ++ creator->volume_api = api; ++ creator->devices = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) device_free); ++ creator->card_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_CARD_PUT], PA_HOOK_NORMAL, card_put_cb, creator); ++ creator->card_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_CARD_UNLINK], PA_HOOK_NORMAL, card_unlink_cb, ++ creator); ++ creator->sink_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_NORMAL, sink_put_cb, creator); ++ creator->sink_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_NORMAL, sink_unlink_cb, ++ creator); ++ creator->source_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL, source_put_cb, ++ creator); ++ creator->source_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_NORMAL, ++ source_unlink_cb, creator); ++ ++ PA_IDXSET_FOREACH(card, api->core->cards, idx) { ++ pa_device_port *port; ++ void *state; ++ ++ PA_HASHMAP_FOREACH(port, card->ports, state) ++ create_device(creator, DEVICE_TYPE_PORT, port); ++ } ++ ++ PA_IDXSET_FOREACH(sink, api->core->sinks, idx) ++ create_device(creator, DEVICE_TYPE_SINK, sink); ++ ++ PA_IDXSET_FOREACH(source, api->core->sources, idx) ++ create_device(creator, DEVICE_TYPE_SOURCE, source); ++ ++ return creator; ++} ++ ++void pa_device_creator_free(pa_device_creator *creator) { ++ pa_assert(creator); ++ ++ if (creator->devices) ++ pa_hashmap_remove_all(creator->devices); ++ ++ if (creator->source_unlink_slot) ++ pa_hook_slot_free(creator->source_unlink_slot); ++ ++ if (creator->source_put_slot) ++ pa_hook_slot_free(creator->source_put_slot); ++ ++ if (creator->sink_unlink_slot) ++ pa_hook_slot_free(creator->sink_unlink_slot); ++ ++ if (creator->sink_put_slot) ++ pa_hook_slot_free(creator->sink_put_slot); ++ ++ if (creator->card_unlink_slot) ++ pa_hook_slot_free(creator->card_unlink_slot); ++ ++ if (creator->card_put_slot) ++ pa_hook_slot_free(creator->card_put_slot); ++ ++ if (creator->devices) ++ pa_hashmap_free(creator->devices); ++ ++ pa_xfree(creator); ++} +diff --git a/src/modules/volume-api/device-creator.h b/src/modules/volume-api/device-creator.h +new file mode 100644 +index 0000000..bcec8d9 +--- /dev/null ++++ b/src/modules/volume-api/device-creator.h +@@ -0,0 +1,32 @@ ++#ifndef foodevicecreatorhfoo ++#define foodevicecreatorhfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++ ++typedef struct pa_device_creator pa_device_creator; ++ ++pa_device_creator *pa_device_creator_new(pa_volume_api *api); ++void pa_device_creator_free(pa_device_creator *creator); ++ ++#endif +diff --git a/src/modules/volume-api/device.c b/src/modules/volume-api/device.c +new file mode 100644 +index 0000000..ea496ba +--- /dev/null ++++ b/src/modules/volume-api/device.c +@@ -0,0 +1,293 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "device.h" ++ ++#include ++#include ++ ++#include ++ ++#include ++ ++pa_device *pa_device_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction, ++ const char * const *device_types, unsigned n_device_types) { ++ pa_device *device; ++ unsigned i; ++ ++ pa_assert(api); ++ pa_assert(name); ++ pa_assert(description); ++ pa_assert(device_types || n_device_types == 0); ++ ++ device = pa_xnew0(pa_device, 1); ++ device->volume_api = api; ++ device->index = pa_volume_api_allocate_device_index(api); ++ pa_assert_se(pa_volume_api_register_name(api, name, false, &device->name) >= 0); ++ device->description = pa_xstrdup(description); ++ device->direction = direction; ++ device->device_types = pa_dynarray_new(pa_xfree); ++ ++ for (i = 0; i < n_device_types; i++) ++ pa_dynarray_append(device->device_types, pa_xstrdup(device_types[i])); ++ ++ device->proplist = pa_proplist_new(); ++ device->use_default_volume_control = true; ++ device->use_default_mute_control = true; ++ ++ return device; ++} ++ ++void pa_device_put(pa_device *device, pa_volume_control *default_volume_control, pa_mute_control *default_mute_control) { ++ char *device_types_str; ++ const char *prop_key; ++ void *state = NULL; ++ ++ pa_assert(device); ++ ++ if (default_volume_control) { ++ device->default_volume_control = default_volume_control; ++ pa_volume_control_add_default_for_device(default_volume_control, device); ++ ++ device->volume_control = default_volume_control; ++ pa_volume_control_add_device(default_volume_control, device); ++ } ++ ++ if (default_mute_control) { ++ device->default_mute_control = default_mute_control; ++ pa_mute_control_add_default_for_device(default_mute_control, device); ++ ++ device->mute_control = default_mute_control; ++ pa_mute_control_add_device(default_mute_control, device); ++ } ++ ++ pa_volume_api_add_device(device->volume_api, device); ++ ++ device->linked = true; ++ ++ device_types_str = pa_join((const char * const *) pa_dynarray_get_raw_array(device->device_types), ++ pa_dynarray_size(device->device_types), ", "); ++ ++ pa_log_debug("Created device #%u.", device->index); ++ pa_log_debug(" Name: %s", device->name); ++ pa_log_debug(" Description: %s", device->description); ++ pa_log_debug(" Direction: %s", pa_direction_to_string(device->direction)); ++ pa_log_debug(" Device Types: %s", *device_types_str ? device_types_str : "(none)"); ++ pa_log_debug(" Volume control: %s", device->volume_control ? device->volume_control->name : "(unset)"); ++ pa_log_debug(" Mute control: %s", device->mute_control ? device->mute_control->name : "(unset)"); ++ pa_log_debug(" Properties:"); ++ ++ while ((prop_key = pa_proplist_iterate(device->proplist, &state))) ++ pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(device->proplist, prop_key))); ++ ++ pa_xfree(device_types_str); ++ ++ pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_PUT], device); ++} ++ ++void pa_device_unlink(pa_device *device) { ++ pa_assert(device); ++ ++ if (device->unlinked) { ++ pa_log_debug("Unlinking device %s (already unlinked, this is a no-op).", device->name); ++ return; ++ } ++ ++ device->unlinked = true; ++ ++ pa_log_debug("Unlinking device %s.", device->name); ++ ++ if (device->linked) ++ pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_UNLINK], device); ++ ++ pa_volume_api_remove_device(device->volume_api, device); ++ ++ if (device->mute_control) { ++ pa_mute_control_remove_device(device->mute_control, device); ++ device->mute_control = NULL; ++ } ++ ++ if (device->default_mute_control) { ++ pa_mute_control_remove_default_for_device(device->default_mute_control, device); ++ device->default_mute_control = NULL; ++ } ++ ++ if (device->volume_control) { ++ pa_volume_control_remove_device(device->volume_control, device); ++ device->volume_control = NULL; ++ } ++ ++ if (device->default_volume_control) { ++ pa_volume_control_remove_default_for_device(device->default_volume_control, device); ++ device->default_volume_control = NULL; ++ } ++} ++ ++void pa_device_free(pa_device *device) { ++ pa_assert(device); ++ ++ if (!device->unlinked) ++ pa_device_unlink(device); ++ ++ if (device->proplist) ++ pa_proplist_free(device->proplist); ++ ++ if (device->device_types) ++ pa_dynarray_free(device->device_types); ++ ++ pa_xfree(device->description); ++ ++ if (device->name) ++ pa_volume_api_unregister_name(device->volume_api, device->name); ++ ++ pa_xfree(device); ++} ++ ++static void set_volume_control_internal(pa_device *device, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(device); ++ ++ old_control = device->volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_volume_control_remove_device(old_control, device); ++ ++ device->volume_control = control; ++ ++ if (control) ++ pa_volume_control_add_device(control, device); ++ ++ if (!device->linked || device->unlinked) ++ return; ++ ++ pa_log_debug("The volume control of device %s changed from %s to %s.", device->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_VOLUME_CONTROL_CHANGED], device); ++} ++ ++void pa_device_set_volume_control(pa_device *device, pa_volume_control *control) { ++ pa_assert(device); ++ ++ device->use_default_volume_control = false; ++ set_volume_control_internal(device, control); ++} ++ ++static void set_mute_control_internal(pa_device *device, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(device); ++ ++ old_control = device->mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_mute_control_remove_device(old_control, device); ++ ++ device->mute_control = control; ++ ++ if (control) ++ pa_mute_control_add_device(control, device); ++ ++ pa_log_debug("The mute control of device %s changed from %s to %s.", device->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_MUTE_CONTROL_CHANGED], device); ++} ++ ++void pa_device_set_mute_control(pa_device *device, pa_mute_control *control) { ++ pa_assert(device); ++ ++ device->use_default_mute_control = false; ++ set_mute_control_internal(device, control); ++} ++ ++void pa_device_description_changed(pa_device *device, const char *new_description) { ++ char *old_description; ++ ++ pa_assert(device); ++ pa_assert(new_description); ++ ++ old_description = device->description; ++ ++ if (pa_streq(new_description, old_description)) ++ return; ++ ++ device->description = pa_xstrdup(new_description); ++ pa_log_debug("The description of device %s changed from \"%s\" to \"%s\".", device->name, old_description, ++ new_description); ++ pa_xfree(old_description); ++ pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_DESCRIPTION_CHANGED], device); ++} ++ ++void pa_device_set_default_volume_control(pa_device *device, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(device); ++ ++ old_control = device->default_volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_volume_control_remove_default_for_device(old_control, device); ++ ++ device->default_volume_control = control; ++ ++ if (control) ++ pa_volume_control_add_default_for_device(control, device); ++ ++ if (device->use_default_volume_control) ++ set_volume_control_internal(device, control); ++} ++ ++void pa_device_set_default_mute_control(pa_device *device, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(device); ++ ++ old_control = device->default_mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_mute_control_remove_default_for_device(old_control, device); ++ ++ device->default_mute_control = control; ++ ++ if (control) ++ pa_mute_control_add_default_for_device(control, device); ++ ++ if (device->use_default_mute_control) ++ set_mute_control_internal(device, control); ++} +diff --git a/src/modules/volume-api/device.h b/src/modules/volume-api/device.h +new file mode 100644 +index 0000000..9eac7e9 +--- /dev/null ++++ b/src/modules/volume-api/device.h +@@ -0,0 +1,76 @@ ++#ifndef foodevicehfoo ++#define foodevicehfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++ ++#include ++ ++typedef struct pa_device pa_device; ++ ++struct pa_device { ++ pa_volume_api *volume_api; ++ uint32_t index; ++ const char *name; ++ char *description; ++ pa_direction_t direction; ++ pa_dynarray *device_types; ++ pa_proplist *proplist; ++ pa_volume_control *volume_control; ++ pa_mute_control *mute_control; ++ ++ /* The device implementation can provide default volume and mute controls, ++ * which are used in case there's no policy module that wants to override ++ * the defaults. */ ++ pa_volume_control *default_volume_control; ++ bool use_default_volume_control; ++ pa_mute_control *default_mute_control; ++ bool use_default_mute_control; ++ ++ bool linked; ++ bool unlinked; ++}; ++ ++pa_device *pa_device_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction, ++ const char * const *device_types, unsigned n_device_types); ++void pa_device_put(pa_device *device, pa_volume_control *default_volume_control, pa_mute_control *default_mute_control); ++void pa_device_unlink(pa_device *device); ++void pa_device_free(pa_device *device); ++ ++/* Called by policy modules. */ ++void pa_device_set_volume_control(pa_device *device, pa_volume_control *control); ++void pa_device_set_mute_control(pa_device *device, pa_mute_control *control); ++ ++/* Called by policy modules. Note that pa_device_set_volume_control() and ++ * pa_device_set_mute_control() automatically disable the corresponding ++ * use_default flags, so these functions are mainly useful for re-enabling the ++ * flags. */ ++void pa_device_set_use_default_volume_control(pa_device *device, bool use); ++void pa_device_set_use_default_mute_control(pa_device *device, bool use); ++ ++/* Called by the device implementation. */ ++void pa_device_description_changed(pa_device *device, const char *new_description); ++void pa_device_set_default_volume_control(pa_device *device, pa_volume_control *control); ++void pa_device_set_default_mute_control(pa_device *device, pa_mute_control *control); ++ ++#endif +diff --git a/src/modules/volume-api/mute-control.c b/src/modules/volume-api/mute-control.c +new file mode 100644 +index 0000000..adc008e +--- /dev/null ++++ b/src/modules/volume-api/mute-control.c +@@ -0,0 +1,306 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "mute-control.h" ++ ++#include ++#include ++#include ++ ++#include ++ ++pa_mute_control *pa_mute_control_new(pa_volume_api *api, const char *name, const char *description) { ++ pa_mute_control *control; ++ ++ pa_assert(api); ++ pa_assert(name); ++ pa_assert(description); ++ ++ control = pa_xnew0(pa_mute_control, 1); ++ control->volume_api = api; ++ control->index = pa_volume_api_allocate_mute_control_index(api); ++ pa_assert_se(pa_volume_api_register_name(api, name, false, &control->name) >= 0); ++ control->description = pa_xstrdup(description); ++ control->proplist = pa_proplist_new(); ++ control->devices = pa_hashmap_new(NULL, NULL); ++ control->default_for_devices = pa_hashmap_new(NULL, NULL); ++ control->streams = pa_hashmap_new(NULL, NULL); ++ control->audio_groups = pa_hashmap_new(NULL, NULL); ++ ++ return control; ++} ++ ++void pa_mute_control_put(pa_mute_control *control, bool initial_mute, bool initial_mute_is_set, ++ pa_mute_control_set_initial_mute_cb_t set_initial_mute_cb) { ++ const char *prop_key; ++ void *state = NULL; ++ ++ pa_assert(control); ++ pa_assert(initial_mute_is_set || control->set_mute); ++ pa_assert(set_initial_mute_cb || !control->set_mute); ++ ++ if (initial_mute_is_set) ++ control->mute = initial_mute; ++ else ++ control->mute = false; ++ ++ if (set_initial_mute_cb) ++ set_initial_mute_cb(control); ++ ++ pa_volume_api_add_mute_control(control->volume_api, control); ++ ++ control->linked = true; ++ ++ pa_log_debug("Created mute control #%u.", control->index); ++ pa_log_debug(" Name: %s", control->name); ++ pa_log_debug(" Description: %s", control->description); ++ pa_log_debug(" Mute: %s", pa_yes_no(control->mute)); ++ pa_log_debug(" Properties:"); ++ ++ while ((prop_key = pa_proplist_iterate(control->proplist, &state))) ++ pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(control->proplist, prop_key))); ++ ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_PUT], control); ++} ++ ++void pa_mute_control_unlink(pa_mute_control *control) { ++ pa_audio_group *group; ++ pa_device *device; ++ pas_stream *stream; ++ ++ pa_assert(control); ++ ++ if (control->unlinked) { ++ pa_log_debug("Unlinking mute control %s (already unlinked, this is a no-op).", control->name); ++ return; ++ } ++ ++ control->unlinked = true; ++ ++ pa_log_debug("Unlinking mute control %s.", control->name); ++ ++ if (control->linked) ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK], control); ++ ++ pa_volume_api_remove_mute_control(control->volume_api, control); ++ ++ while ((group = pa_hashmap_first(control->audio_groups))) ++ pa_audio_group_set_mute_control(group, NULL); ++ ++ while ((stream = pa_hashmap_first(control->streams))) ++ pas_stream_set_mute_control(stream, NULL); ++ ++ while ((device = pa_hashmap_first(control->default_for_devices))) ++ pa_device_set_default_mute_control(device, NULL); ++ ++ while ((device = pa_hashmap_first(control->devices))) { ++ /* Why do we have this assertion here? The concern is that if we call ++ * pa_device_set_mute_control() for some device that has the ++ * use_default_mute_control flag set, then that flag will be unset as ++ * a side effect, and we don't want that side effect. This assertion ++ * should be safe, because we just called ++ * pa_device_set_default_mute_control(NULL) for each device that this ++ * control was the default for, and that should ensure that we don't ++ * any more hold any references to devices that used to use this ++ * control as the default. */ ++ pa_assert(!device->use_default_mute_control); ++ pa_device_set_mute_control(device, NULL); ++ } ++} ++ ++void pa_mute_control_free(pa_mute_control *control) { ++ pa_assert(control); ++ ++ if (!control->unlinked) ++ pa_mute_control_unlink(control); ++ ++ if (control->audio_groups) { ++ pa_assert(pa_hashmap_isempty(control->audio_groups)); ++ pa_hashmap_free(control->audio_groups); ++ } ++ ++ if (control->streams) { ++ pa_assert(pa_hashmap_isempty(control->streams)); ++ pa_hashmap_free(control->streams); ++ } ++ ++ if (control->default_for_devices) { ++ pa_assert(pa_hashmap_isempty(control->default_for_devices)); ++ pa_hashmap_free(control->default_for_devices); ++ } ++ ++ if (control->devices) { ++ pa_assert(pa_hashmap_isempty(control->devices)); ++ pa_hashmap_free(control->devices); ++ } ++ ++ if (control->proplist) ++ pa_proplist_free(control->proplist); ++ ++ pa_xfree(control->description); ++ ++ if (control->name) ++ pa_volume_api_unregister_name(control->volume_api, control->name); ++ ++ pa_xfree(control); ++} ++ ++void pa_mute_control_set_owner_audio_group(pa_mute_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ control->owner_audio_group = group; ++} ++ ++static void set_mute_internal(pa_mute_control *control, bool mute) { ++ bool old_mute; ++ ++ pa_assert(control); ++ ++ old_mute = control->mute; ++ ++ if (mute == old_mute) ++ return; ++ ++ control->mute = mute; ++ ++ if (!control->linked || control->unlinked) ++ return; ++ ++ pa_log_debug("The mute of mute control %s changed from %s to %s.", control->name, pa_yes_no(old_mute), ++ pa_yes_no(control->mute)); ++ ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_MUTE_CHANGED], control); ++} ++ ++int pa_mute_control_set_mute(pa_mute_control *control, bool mute) { ++ int r; ++ ++ pa_assert(control); ++ ++ if (!control->set_mute) { ++ pa_log_info("Tried to set the mute of mute control %s, but the mute control doesn't support the operation.", ++ control->name); ++ return -PA_ERR_NOTSUPPORTED; ++ } ++ ++ if (mute == control->mute) ++ return 0; ++ ++ control->set_mute_in_progress = true; ++ r = control->set_mute(control, mute); ++ control->set_mute_in_progress = false; ++ ++ if (r >= 0) ++ set_mute_internal(control, mute); ++ ++ return r; ++} ++ ++void pa_mute_control_description_changed(pa_mute_control *control, const char *new_description) { ++ char *old_description; ++ ++ pa_assert(control); ++ pa_assert(new_description); ++ ++ old_description = control->description; ++ ++ if (pa_streq(new_description, old_description)) ++ return; ++ ++ control->description = pa_xstrdup(new_description); ++ pa_log_debug("The description of mute control %s changed from \"%s\" to \"%s\".", control->name, old_description, ++ new_description); ++ pa_xfree(old_description); ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_DESCRIPTION_CHANGED], control); ++} ++ ++void pa_mute_control_mute_changed(pa_mute_control *control, bool new_mute) { ++ pa_assert(control); ++ ++ if (!control->linked) ++ return; ++ ++ if (control->set_mute_in_progress) ++ return; ++ ++ set_mute_internal(control, new_mute); ++} ++ ++void pa_mute_control_add_device(pa_mute_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_put(control->devices, device, device) >= 0); ++} ++ ++void pa_mute_control_remove_device(pa_mute_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_remove(control->devices, device)); ++} ++ ++void pa_mute_control_add_default_for_device(pa_mute_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_put(control->default_for_devices, device, device) >= 0); ++} ++ ++void pa_mute_control_remove_default_for_device(pa_mute_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_remove(control->default_for_devices, device)); ++} ++ ++void pa_mute_control_add_stream(pa_mute_control *control, pas_stream *stream) { ++ pa_assert(control); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_put(control->streams, stream, stream) >= 0); ++} ++ ++void pa_mute_control_remove_stream(pa_mute_control *control, pas_stream *stream) { ++ pa_assert(control); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_remove(control->streams, stream)); ++} ++ ++void pa_mute_control_add_audio_group(pa_mute_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ pa_assert_se(pa_hashmap_put(control->audio_groups, group, group) >= 0); ++} ++ ++void pa_mute_control_remove_audio_group(pa_mute_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ pa_assert_se(pa_hashmap_remove(control->audio_groups, group)); ++} +diff --git a/src/modules/volume-api/mute-control.h b/src/modules/volume-api/mute-control.h +new file mode 100644 +index 0000000..1f70a43 +--- /dev/null ++++ b/src/modules/volume-api/mute-control.h +@@ -0,0 +1,102 @@ ++#ifndef foomutecontrolhfoo ++#define foomutecontrolhfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++ ++typedef struct pa_mute_control pa_mute_control; ++ ++struct pa_mute_control { ++ pa_volume_api *volume_api; ++ uint32_t index; ++ const char *name; ++ char *description; ++ pa_proplist *proplist; ++ bool mute; ++ ++ /* If this mute control is the "own mute control" of an audio group, this ++ * is set to point to that group, otherwise this is NULL. */ ++ pa_audio_group *owner_audio_group; ++ ++ pa_hashmap *devices; /* pa_device -> pa_device (hashmap-as-a-set) */ ++ pa_hashmap *default_for_devices; /* pa_device -> pa_device (hashmap-as-a-set) */ ++ pa_hashmap *streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ ++ pa_hashmap *audio_groups; /* pa_audio_group -> pa_audio_group (hashmap-as-a-set) */ ++ ++ bool linked; ++ bool unlinked; ++ bool set_mute_in_progress; ++ ++ /* Called from pa_mute_control_set_mute(). The implementation is expected ++ * to return a negative error code on failure. May be NULL, if the mute ++ * control is read-only. */ ++ int (*set_mute)(pa_mute_control *control, bool mute); ++ ++ void *userdata; ++}; ++ ++pa_mute_control *pa_mute_control_new(pa_volume_api *api, const char *name, const char *description); ++ ++typedef void (*pa_mute_control_set_initial_mute_cb_t)(pa_mute_control *control); ++ ++/* initial_mute is the preferred initial mute of the mute control ++ * implementation. It may be unset, if the implementation doesn't care about ++ * the initial state of the mute control. Read-only mute controls, however, ++ * must always set initial_mute. ++ * ++ * The implementation's initial mute preference may be overridden by policy, if ++ * the mute control isn't read-only. When the final initial mute is known, the ++ * the implementation is notified via set_initial_mute_cb (the mute can be read ++ * from control->mute). set_initial_mute_cb may be NULL, if the mute control is ++ * read-only. */ ++void pa_mute_control_put(pa_mute_control *control, bool initial_mute, bool initial_mute_is_set, ++ pa_mute_control_set_initial_mute_cb_t set_initial_mute_cb); ++ ++void pa_mute_control_unlink(pa_mute_control *control); ++void pa_mute_control_free(pa_mute_control *control); ++ ++/* Called by audio-group.c only. */ ++void pa_mute_control_set_owner_audio_group(pa_mute_control *control, pa_audio_group *group); ++ ++/* Called by clients and policy modules. */ ++int pa_mute_control_set_mute(pa_mute_control *control, bool mute); ++ ++/* Called by the mute control implementation. */ ++void pa_mute_control_description_changed(pa_mute_control *control, const char *new_description); ++void pa_mute_control_mute_changed(pa_mute_control *control, bool new_mute); ++ ++/* Called from device.c only. */ ++void pa_mute_control_add_device(pa_mute_control *control, pa_device *device); ++void pa_mute_control_remove_device(pa_mute_control *control, pa_device *device); ++void pa_mute_control_add_default_for_device(pa_mute_control *control, pa_device *device); ++void pa_mute_control_remove_default_for_device(pa_mute_control *control, pa_device *device); ++ ++/* Called from sstream.c only. */ ++void pa_mute_control_add_stream(pa_mute_control *control, pas_stream *stream); ++void pa_mute_control_remove_stream(pa_mute_control *control, pas_stream *stream); ++ ++/* Called from audio-group.c only. */ ++void pa_mute_control_add_audio_group(pa_mute_control *control, pa_audio_group *group); ++void pa_mute_control_remove_audio_group(pa_mute_control *control, pa_audio_group *group); ++ ++#endif +diff --git a/src/modules/volume-api/sstream.c b/src/modules/volume-api/sstream.c +new file mode 100644 +index 0000000..e3531a8 +--- /dev/null ++++ b/src/modules/volume-api/sstream.c +@@ -0,0 +1,366 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "sstream.h" ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++pas_stream *pas_stream_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction) { ++ pas_stream *stream; ++ ++ pa_assert(api); ++ pa_assert(name); ++ pa_assert(description); ++ ++ stream = pa_xnew0(pas_stream, 1); ++ stream->volume_api = api; ++ stream->index = pa_volume_api_allocate_stream_index(api); ++ pa_assert_se(pa_volume_api_register_name(api, name, false, &stream->name) >= 0); ++ stream->description = pa_xstrdup(description); ++ stream->direction = direction; ++ stream->proplist = pa_proplist_new(); ++ stream->use_default_volume_control = true; ++ stream->use_default_mute_control = true; ++ ++ return stream; ++} ++ ++static void set_volume_control_internal(pas_stream *stream, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(stream); ++ ++ old_control = stream->volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) { ++ /* If the old control pointed to the own volume control of an audio ++ * group, then the stream's audio group for volume needs to be ++ * updated. We set it to NULL here, and if it should be non-NULL, that ++ * will be fixed very soon (a few lines down). */ ++ pas_stream_set_audio_group_for_volume(stream, NULL); ++ ++ pa_volume_control_remove_stream(old_control, stream); ++ } ++ ++ stream->volume_control = control; ++ ++ if (control) { ++ pa_volume_control_add_stream(control, stream); ++ pas_stream_set_audio_group_for_volume(stream, control->owner_audio_group); ++ } ++ ++ if (!stream->linked || stream->unlinked) ++ return; ++ ++ pa_log_debug("The volume control of stream %s changed from %s to %s.", stream->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED], stream); ++} ++ ++static void set_mute_control_internal(pas_stream *stream, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(stream); ++ ++ old_control = stream->mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) { ++ /* If the old control pointed to the own mute control of an audio ++ * group, then the stream's audio group for mute needs to be updated. ++ * We set it to NULL here, and if it should be non-NULL, that will be ++ * fixed very soon (a few lines down). */ ++ pas_stream_set_audio_group_for_mute(stream, NULL); ++ ++ pa_mute_control_remove_stream(old_control, stream); ++ } ++ ++ stream->mute_control = control; ++ ++ if (control) { ++ pa_mute_control_add_stream(control, stream); ++ pas_stream_set_audio_group_for_mute(stream, control->owner_audio_group); ++ } ++ ++ if (!stream->linked || stream->unlinked) ++ return; ++ ++ pa_log_debug("The mute control of stream %s changed from %s to %s.", stream->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_MUTE_CONTROL_CHANGED], stream); ++} ++ ++void pas_stream_put(pas_stream *stream, pa_proplist *initial_properties) { ++ const char *prop_key; ++ void *state = NULL; ++ ++ pa_assert(stream); ++ pa_assert(!stream->create_own_volume_control || stream->delete_own_volume_control); ++ pa_assert(!stream->create_own_mute_control || stream->delete_own_mute_control); ++ ++ if (initial_properties) ++ pa_proplist_update(stream->proplist, PA_UPDATE_REPLACE, initial_properties); ++ ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_VOLUME_CONTROL], stream); ++ ++ if (stream->use_default_volume_control) ++ set_volume_control_internal(stream, stream->own_volume_control); ++ ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_MUTE_CONTROL], stream); ++ ++ if (stream->use_default_mute_control) ++ set_mute_control_internal(stream, stream->own_mute_control); ++ ++ pa_volume_api_add_stream(stream->volume_api, stream); ++ ++ stream->linked = true; ++ ++ pa_log_debug("Created stream #%u.", stream->index); ++ pa_log_debug(" Name: %s", stream->name); ++ pa_log_debug(" Description: %s", stream->description); ++ pa_log_debug(" Direction: %s", pa_direction_to_string(stream->direction)); ++ pa_log_debug(" Volume control: %s", stream->volume_control ? stream->volume_control->name : "(unset)"); ++ pa_log_debug(" Mute control: %s", stream->mute_control ? stream->mute_control->name : "(unset)"); ++ pa_log_debug(" Properties:"); ++ ++ while ((prop_key = pa_proplist_iterate(stream->proplist, &state))) ++ pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(stream->proplist, prop_key))); ++ ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_PUT], stream); ++} ++ ++void pas_stream_unlink(pas_stream *stream) { ++ pa_assert(stream); ++ ++ if (stream->unlinked) { ++ pa_log_debug("Unlinking stream %s (already unlinked, this is a no-op).", stream->name); ++ return; ++ } ++ ++ stream->unlinked = true; ++ ++ pa_log_debug("Unlinking stream %s.", stream->name); ++ ++ if (stream->linked) ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_UNLINK], stream); ++ ++ pa_volume_api_remove_stream(stream->volume_api, stream); ++ ++ pas_stream_set_audio_group_for_mute(stream, NULL); ++ pas_stream_set_audio_group_for_volume(stream, NULL); ++ pas_stream_set_mute_control(stream, NULL); ++ pas_stream_set_volume_control(stream, NULL); ++ pas_stream_set_have_own_mute_control(stream, false); ++ pas_stream_set_have_own_volume_control(stream, false); ++} ++ ++void pas_stream_free(pas_stream *stream) { ++ pa_assert(stream); ++ ++ if (!stream->unlinked) ++ pas_stream_unlink(stream); ++ ++ if (stream->proplist) ++ pa_proplist_free(stream->proplist); ++ ++ pa_xfree(stream->description); ++ ++ if (stream->name) ++ pa_volume_api_unregister_name(stream->volume_api, stream->name); ++ ++ pa_xfree(stream); ++} ++ ++int pas_stream_set_have_own_volume_control(pas_stream *stream, bool have) { ++ pa_assert(stream); ++ ++ if (have == stream->have_own_volume_control) ++ return 0; ++ ++ if (have) { ++ pa_assert(!stream->own_volume_control); ++ ++ if (!stream->create_own_volume_control) { ++ pa_log_debug("Stream %s doesn't support own volume control.", stream->name); ++ return -PA_ERR_NOTSUPPORTED; ++ } ++ ++ stream->own_volume_control = stream->create_own_volume_control(stream); ++ } else { ++ stream->delete_own_volume_control(stream); ++ stream->own_volume_control = NULL; ++ } ++ ++ stream->have_own_volume_control = have; ++ ++ return 0; ++} ++ ++int pas_stream_set_have_own_mute_control(pas_stream *stream, bool have) { ++ pa_assert(stream); ++ ++ if (have == stream->have_own_mute_control) ++ return 0; ++ ++ if (have) { ++ pa_assert(!stream->own_mute_control); ++ ++ if (!stream->create_own_mute_control) { ++ pa_log_debug("Stream %s doesn't support own mute control.", stream->name); ++ return -PA_ERR_NOTSUPPORTED; ++ } ++ ++ stream->own_mute_control = stream->create_own_mute_control(stream); ++ } else { ++ stream->delete_own_mute_control(stream); ++ stream->own_mute_control = NULL; ++ } ++ ++ stream->have_own_mute_control = have; ++ ++ return 0; ++} ++ ++void pas_stream_set_volume_control(pas_stream *stream, pa_volume_control *control) { ++ pa_assert(stream); ++ ++ stream->use_default_volume_control = false; ++ ++ if (stream->volume_control_binding) { ++ pa_binding_free(stream->volume_control_binding); ++ stream->volume_control_binding = NULL; ++ } ++ ++ set_volume_control_internal(stream, control); ++} ++ ++void pas_stream_set_mute_control(pas_stream *stream, pa_mute_control *control) { ++ pa_assert(stream); ++ ++ stream->use_default_mute_control = false; ++ ++ if (stream->mute_control_binding) { ++ pa_binding_free(stream->mute_control_binding); ++ stream->mute_control_binding = NULL; ++ } ++ ++ set_mute_control_internal(stream, control); ++} ++ ++void pas_stream_bind_volume_control(pas_stream *stream, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = stream, ++ .set_value = (pa_binding_set_value_cb_t) set_volume_control_internal, ++ }; ++ ++ pa_assert(stream); ++ pa_assert(target_info); ++ ++ stream->use_default_volume_control = false; ++ ++ if (stream->volume_control_binding) ++ pa_binding_free(stream->volume_control_binding); ++ ++ stream->volume_control_binding = pa_binding_new(stream->volume_api, &owner_info, target_info); ++} ++ ++void pas_stream_bind_mute_control(pas_stream *stream, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = stream, ++ .set_value = (pa_binding_set_value_cb_t) set_mute_control_internal, ++ }; ++ ++ pa_assert(stream); ++ pa_assert(target_info); ++ ++ stream->use_default_mute_control = false; ++ ++ if (stream->mute_control_binding) ++ pa_binding_free(stream->mute_control_binding); ++ ++ stream->mute_control_binding = pa_binding_new(stream->volume_api, &owner_info, target_info); ++} ++ ++void pas_stream_description_changed(pas_stream *stream, const char *new_description) { ++ char *old_description; ++ ++ pa_assert(stream); ++ pa_assert(new_description); ++ ++ old_description = stream->description; ++ ++ if (pa_streq(new_description, old_description)) ++ return; ++ ++ stream->description = pa_xstrdup(new_description); ++ pa_log_debug("The description of stream %s changed from \"%s\" to \"%s\".", stream->name, old_description, ++ new_description); ++ pa_xfree(old_description); ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_DESCRIPTION_CHANGED], stream); ++} ++ ++void pas_stream_set_audio_group_for_volume(pas_stream *stream, pa_audio_group *group) { ++ pa_assert(stream); ++ ++ if (group == stream->audio_group_for_volume) ++ return; ++ ++ if (stream->audio_group_for_volume) ++ pa_audio_group_remove_volume_stream(stream->audio_group_for_volume, stream); ++ ++ stream->audio_group_for_volume = group; ++ ++ if (group) ++ pa_audio_group_add_volume_stream(group, stream); ++} ++ ++void pas_stream_set_audio_group_for_mute(pas_stream *stream, pa_audio_group *group) { ++ pa_assert(stream); ++ ++ if (group == stream->audio_group_for_mute) ++ return; ++ ++ if (stream->audio_group_for_mute) ++ pa_audio_group_remove_mute_stream(stream->audio_group_for_mute, stream); ++ ++ stream->audio_group_for_mute = group; ++ ++ if (group) ++ pa_audio_group_add_mute_stream(group, stream); ++} +diff --git a/src/modules/volume-api/sstream.h b/src/modules/volume-api/sstream.h +new file mode 100644 +index 0000000..a65b34c +--- /dev/null ++++ b/src/modules/volume-api/sstream.h +@@ -0,0 +1,108 @@ ++#ifndef foosstreamhfoo ++#define foosstreamhfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++ ++/* We use the "pas_" prefix in pas_stream, because there's already pa_stream in ++ * the client API, and there's no good alternative term for streams. The 's' in ++ * "pas" means "server", i.e. the point is that this stuff is for servers, ++ * while pa_stream is for clients. */ ++ ++typedef struct pas_stream pas_stream; ++ ++struct pas_stream { ++ pa_volume_api *volume_api; ++ uint32_t index; ++ const char *name; ++ char *description; ++ pa_direction_t direction; ++ pa_proplist *proplist; ++ pa_volume_control *volume_control; ++ pa_mute_control *mute_control; ++ bool use_default_volume_control; ++ bool use_default_mute_control; ++ bool have_own_volume_control; ++ bool have_own_mute_control; ++ pa_volume_control *own_volume_control; ++ pa_mute_control *own_mute_control; ++ ++ pa_binding *volume_control_binding; ++ pa_binding *mute_control_binding; ++ pa_audio_group *audio_group_for_volume; ++ pa_audio_group *audio_group_for_mute; ++ ++ bool linked; ++ bool unlinked; ++ ++ /* Called when the own volume control is enabled. The callback ++ * implementation should return a new linked volume control object. The ++ * callback may be NULL, in which case the own volume control can't be ++ * enabled. */ ++ pa_volume_control *(*create_own_volume_control)(pas_stream *stream); ++ ++ /* Called when the own volume control is disabled. The implementation ++ * should free stream->own_volume_control. The callback may be NULL only if ++ * create_own_volume_control is NULL also. */ ++ void (*delete_own_volume_control)(pas_stream *stream); ++ ++ /* Called when the own mute control is enabled. The callback implementation ++ * should return a new linked mute control object. The callback may be ++ * NULL, in which case the own mute control can't be enabled. */ ++ pa_mute_control *(*create_own_mute_control)(pas_stream *stream); ++ ++ /* Called when the own mute control is disabled. The implementation should ++ * free stream->own_mute_control. The callback may be NULL only if ++ * create_own_mute_control is NULL also. */ ++ void (*delete_own_mute_control)(pas_stream *stream); ++ ++ void *userdata; ++}; ++ ++pas_stream *pas_stream_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction); ++void pas_stream_put(pas_stream *stream, pa_proplist *initial_properties); ++void pas_stream_unlink(pas_stream *stream); ++void pas_stream_free(pas_stream *stream); ++ ++/* Called by the stream implementation and possibly by policy modules. ++ * Enabling own controls may fail (the stream may not support own controls), ++ * disabling will never fail. */ ++int pas_stream_set_have_own_volume_control(pas_stream *stream, bool have); ++int pas_stream_set_have_own_mute_control(pas_stream *stream, bool have); ++ ++/* Called by policy modules. */ ++void pas_stream_set_volume_control(pas_stream *stream, pa_volume_control *control); ++void pas_stream_set_mute_control(pas_stream *stream, pa_mute_control *control); ++void pas_stream_bind_volume_control(pas_stream *stream, pa_binding_target_info *target_info); ++void pas_stream_bind_mute_control(pas_stream *stream, pa_binding_target_info *target_info); ++ ++/* Called by the stream implementation. */ ++void pas_stream_description_changed(pas_stream *stream, const char *new_description); ++ ++/* Called by audio-group.c only. Adding a stream to an audio group happens ++ * implicitly when the volume or mute control of a stream is set to point to ++ * the own control of an audio group. */ ++void pas_stream_set_audio_group_for_volume(pas_stream *stream, pa_audio_group *group); ++void pas_stream_set_audio_group_for_mute(pas_stream *stream, pa_audio_group *group); ++ ++#endif +diff --git a/src/modules/volume-api/stream-creator.c b/src/modules/volume-api/stream-creator.c +new file mode 100644 +index 0000000..2bd0053 +--- /dev/null ++++ b/src/modules/volume-api/stream-creator.c +@@ -0,0 +1,691 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "stream-creator.h" ++ ++#include ++#include ++#include ++ ++#include ++#include ++ ++struct pa_stream_creator { ++ pa_volume_api *volume_api; ++ pa_hashmap *streams; /* pa_sink_input/pa_source_output -> struct stream */ ++ pa_hook_slot *sink_input_put_slot; ++ pa_hook_slot *sink_input_unlink_slot; ++ pa_hook_slot *source_output_put_slot; ++ pa_hook_slot *source_output_unlink_slot; ++}; ++ ++enum stream_type { ++ STREAM_TYPE_SINK_INPUT, ++ STREAM_TYPE_SOURCE_OUTPUT, ++}; ++ ++struct stream { ++ pa_stream_creator *creator; ++ enum stream_type type; ++ pa_sink_input *sink_input; ++ pa_source_output *source_output; ++ pa_client *client; ++ pas_stream *stream; ++ ++ bool unlinked; ++ ++ pa_hook_slot *proplist_changed_slot; ++ pa_hook_slot *client_proplist_changed_slot; ++ pa_hook_slot *volume_changed_slot; ++ pa_hook_slot *mute_changed_slot; ++}; ++ ++static char *get_stream_volume_and_mute_control_description_malloc(struct stream *stream) { ++ const char *application_name = NULL; ++ char *description; ++ ++ pa_assert(stream); ++ ++ if (stream->client) ++ application_name = pa_proplist_gets(stream->client->proplist, PA_PROP_APPLICATION_NAME); ++ ++ if (application_name) ++ description = pa_sprintf_malloc("%s: %s", application_name, stream->stream->description); ++ else ++ description = pa_xstrdup(stream->stream->description); ++ ++ return description; ++} ++ ++static int volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance) { ++ struct stream *stream; ++ pa_bvolume bvolume; ++ pa_cvolume cvolume; ++ ++ pa_assert(control); ++ pa_assert(volume); ++ ++ stream = control->userdata; ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ pa_bvolume_from_cvolume(&bvolume, &stream->sink_input->volume, &stream->sink_input->channel_map); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ pa_bvolume_from_cvolume(&bvolume, &stream->source_output->volume, &stream->source_output->channel_map); ++ break; ++ } ++ ++ if (set_volume) ++ bvolume.volume = volume->volume; ++ ++ if (set_balance) ++ pa_bvolume_copy_balance(&bvolume, volume); ++ ++ pa_bvolume_to_cvolume(&bvolume, &cvolume); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ pa_sink_input_set_volume(stream->sink_input, &cvolume, true, true); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ pa_source_output_set_volume(stream->source_output, &cvolume, true, true); ++ break; ++ } ++ ++ return 0; ++} ++ ++static pa_hook_result_t sink_input_or_source_output_volume_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct stream *stream = userdata; ++ pa_sink_input *input = NULL; ++ pa_source_output *output = NULL; ++ pa_bvolume bvolume; ++ ++ pa_assert(stream); ++ pa_assert(call_data); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ input = call_data; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ output = call_data; ++ break; ++ } ++ ++ if ((input && input != stream->sink_input) || (output && output != stream->source_output)) ++ return PA_HOOK_OK; ++ ++ if (input) ++ pa_bvolume_from_cvolume(&bvolume, &input->volume, &input->channel_map); ++ else ++ pa_bvolume_from_cvolume(&bvolume, &output->volume, &output->channel_map); ++ ++ pa_volume_control_volume_changed(stream->stream->own_volume_control, &bvolume, true, true); ++ ++ return PA_HOOK_OK; ++} ++ ++static void volume_control_set_initial_volume_cb(pa_volume_control *control) { ++ struct stream *stream; ++ pa_cvolume cvolume; ++ ++ pa_assert(control); ++ ++ stream = control->userdata; ++ pa_bvolume_to_cvolume(&control->volume, &cvolume); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ pa_sink_input_set_volume(stream->sink_input, &cvolume, true, true); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ pa_source_output_set_volume(stream->source_output, &cvolume, true, true); ++ break; ++ } ++} ++ ++static int mute_control_set_mute_cb(pa_mute_control *control, bool mute) { ++ struct stream *stream; ++ ++ pa_assert(control); ++ ++ stream = control->userdata; ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ pa_sink_input_set_mute(stream->sink_input, mute, true); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ pa_source_output_set_mute(stream->source_output, mute, true); ++ break; ++ } ++ ++ return 0; ++} ++ ++static pa_hook_result_t sink_input_or_source_output_mute_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct stream *stream = userdata; ++ pa_sink_input *input = NULL; ++ pa_source_output *output = NULL; ++ bool mute; ++ ++ pa_assert(stream); ++ pa_assert(call_data); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ input = call_data; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ output = call_data; ++ break; ++ } ++ ++ if ((input && input != stream->sink_input) || (output && output != stream->source_output)) ++ return PA_HOOK_OK; ++ ++ if (input) ++ mute = input->muted; ++ else ++ mute = output->muted; ++ ++ pa_mute_control_mute_changed(stream->stream->own_mute_control, mute); ++ ++ return PA_HOOK_OK; ++} ++ ++static void mute_control_set_initial_mute_cb(pa_mute_control *control) { ++ struct stream *stream; ++ ++ pa_assert(control); ++ ++ stream = control->userdata; ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ pa_sink_input_set_mute(stream->sink_input, control->mute, true); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ pa_source_output_set_mute(stream->source_output, control->mute, true); ++ break; ++ } ++} ++ ++static const char *get_sink_input_description(pa_sink_input *input) { ++ const char *description; ++ ++ pa_assert(input); ++ ++ description = pa_proplist_gets(input->proplist, PA_PROP_MEDIA_NAME); ++ if (description) ++ return description; ++ ++ return NULL; ++} ++ ++static const char *get_source_output_description(pa_source_output *output) { ++ const char *description; ++ ++ pa_assert(output); ++ ++ description = pa_proplist_gets(output->proplist, PA_PROP_MEDIA_NAME); ++ if (description) ++ return description; ++ ++ return NULL; ++} ++ ++static pa_volume_control *stream_create_own_volume_control_cb(pas_stream *s) { ++ struct stream *stream; ++ const char *name = NULL; ++ char *description; ++ pa_volume_control *control; ++ pa_bvolume volume; ++ ++ pa_assert(s); ++ ++ stream = s->userdata; ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ name = "sink-input-volume-control"; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ name = "source-output-volume-control"; ++ break; ++ } ++ ++ description = get_stream_volume_and_mute_control_description_malloc(stream); ++ control = pa_volume_control_new(stream->creator->volume_api, name, description, true, false); ++ pa_xfree(description); ++ control->set_volume = volume_control_set_volume_cb; ++ control->userdata = stream; ++ ++ pa_assert(!stream->volume_changed_slot); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ stream->volume_changed_slot = ++ pa_hook_connect(&stream->sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED], PA_HOOK_NORMAL, ++ sink_input_or_source_output_volume_changed_cb, stream); ++ pa_bvolume_from_cvolume(&volume, &stream->sink_input->volume, &stream->sink_input->channel_map); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ stream->volume_changed_slot = ++ pa_hook_connect(&stream->source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED], ++ PA_HOOK_NORMAL, sink_input_or_source_output_volume_changed_cb, stream); ++ pa_bvolume_from_cvolume(&volume, &stream->source_output->volume, &stream->source_output->channel_map); ++ break; ++ } ++ ++ pa_volume_control_put(control, &volume, volume_control_set_initial_volume_cb); ++ ++ return control; ++} ++ ++static void stream_delete_own_volume_control_cb(pas_stream *s) { ++ struct stream *stream; ++ ++ pa_assert(s); ++ ++ stream = s->userdata; ++ pa_hook_slot_free(stream->volume_changed_slot); ++ stream->volume_changed_slot = NULL; ++ pa_volume_control_free(s->own_volume_control); ++} ++ ++static pa_mute_control *stream_create_own_mute_control_cb(pas_stream *s) { ++ struct stream *stream; ++ const char *name = NULL; ++ char *description; ++ pa_mute_control *control; ++ bool mute = false; ++ ++ pa_assert(s); ++ ++ stream = s->userdata; ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ name = "sink-input-mute-control"; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ name = "source-output-mute-control"; ++ break; ++ } ++ ++ description = get_stream_volume_and_mute_control_description_malloc(stream); ++ control = pa_mute_control_new(stream->creator->volume_api, name, description); ++ pa_xfree(description); ++ control->set_mute = mute_control_set_mute_cb; ++ control->userdata = stream; ++ ++ pa_assert(!stream->mute_changed_slot); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ stream->mute_changed_slot = ++ pa_hook_connect(&stream->sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], PA_HOOK_NORMAL, ++ sink_input_or_source_output_mute_changed_cb, stream); ++ mute = stream->sink_input->muted; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ stream->mute_changed_slot = ++ pa_hook_connect(&stream->source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED], ++ PA_HOOK_NORMAL, sink_input_or_source_output_mute_changed_cb, stream); ++ mute = stream->source_output->muted; ++ break; ++ } ++ ++ pa_mute_control_put(control, mute, true, mute_control_set_initial_mute_cb); ++ ++ return control; ++} ++ ++static void stream_delete_own_mute_control_cb(pas_stream *s) { ++ struct stream *stream; ++ ++ pa_assert(s); ++ ++ stream = s->userdata; ++ pa_hook_slot_free(stream->mute_changed_slot); ++ stream->mute_changed_slot = NULL; ++ pa_mute_control_free(s->own_mute_control); ++} ++ ++static pa_hook_result_t sink_input_or_source_output_proplist_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct stream *stream = userdata; ++ pa_sink_input *input = NULL; ++ pa_source_output *output = NULL; ++ const char *new_stream_description = NULL; ++ char *new_control_description; ++ ++ pa_assert(stream); ++ pa_assert(call_data); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ input = call_data; ++ ++ if (input != stream->sink_input) ++ return PA_HOOK_OK; ++ ++ new_stream_description = get_sink_input_description(input); ++ if (!new_stream_description) ++ new_stream_description = stream->stream->name; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ output = call_data; ++ ++ if (output != stream->source_output) ++ return PA_HOOK_OK; ++ ++ new_stream_description = get_source_output_description(output); ++ if (!new_stream_description) ++ new_stream_description = stream->stream->name; ++ break; ++ } ++ ++ pas_stream_description_changed(stream->stream, new_stream_description); ++ ++ new_control_description = get_stream_volume_and_mute_control_description_malloc(stream); ++ ++ if (stream->stream->own_volume_control) ++ pa_volume_control_description_changed(stream->stream->own_volume_control, new_control_description); ++ ++ if (stream->stream->own_mute_control) ++ pa_mute_control_description_changed(stream->stream->own_mute_control, new_control_description); ++ ++ pa_xfree(new_control_description); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t client_proplist_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct stream *stream = userdata; ++ pa_client *client = call_data; ++ char *description; ++ ++ pa_assert(stream); ++ pa_assert(client); ++ ++ if (client != stream->client) ++ return PA_HOOK_OK; ++ ++ description = get_stream_volume_and_mute_control_description_malloc(stream); ++ ++ if (stream->stream->own_volume_control) ++ pa_volume_control_description_changed(stream->stream->own_volume_control, description); ++ ++ if (stream->stream->own_mute_control) ++ pa_mute_control_description_changed(stream->stream->own_mute_control, description); ++ ++ pa_xfree(description); ++ ++ return PA_HOOK_OK; ++} ++ ++static struct stream *stream_new(pa_stream_creator *creator, enum stream_type type, void *core_stream) { ++ struct stream *stream; ++ const char *name = NULL; ++ const char *description = NULL; ++ pa_direction_t direction = PA_DIRECTION_OUTPUT; ++ ++ pa_assert(creator); ++ pa_assert(core_stream); ++ ++ stream = pa_xnew0(struct stream, 1); ++ stream->creator = creator; ++ stream->type = type; ++ ++ switch (type) { ++ case STREAM_TYPE_SINK_INPUT: ++ stream->sink_input = core_stream; ++ stream->client = stream->sink_input->client; ++ name = "sink-input-stream"; ++ ++ description = get_sink_input_description(stream->sink_input); ++ if (!description) ++ description = name; ++ ++ direction = PA_DIRECTION_OUTPUT; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ stream->source_output = core_stream; ++ stream->client = stream->source_output->client; ++ name = "source-output-stream"; ++ ++ description = get_source_output_description(stream->source_output); ++ if (!description) ++ description = name; ++ ++ direction = PA_DIRECTION_INPUT; ++ break; ++ } ++ ++ stream->stream = pas_stream_new(creator->volume_api, name, description, direction); ++ stream->stream->create_own_volume_control = stream_create_own_volume_control_cb; ++ stream->stream->delete_own_volume_control = stream_delete_own_volume_control_cb; ++ stream->stream->create_own_mute_control = stream_create_own_mute_control_cb; ++ stream->stream->delete_own_mute_control = stream_delete_own_mute_control_cb; ++ stream->stream->userdata = stream; ++ pas_stream_set_have_own_volume_control(stream->stream, true); ++ pas_stream_set_have_own_mute_control(stream->stream, true); ++ ++ switch (type) { ++ case STREAM_TYPE_SINK_INPUT: ++ stream->proplist_changed_slot = ++ pa_hook_connect(&stream->sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_NORMAL, ++ sink_input_or_source_output_proplist_changed_cb, stream); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ stream->proplist_changed_slot = ++ pa_hook_connect(&stream->source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], ++ PA_HOOK_NORMAL, sink_input_or_source_output_proplist_changed_cb, stream); ++ break; ++ } ++ ++ stream->client_proplist_changed_slot = ++ pa_hook_connect(&stream->creator->volume_api->core->hooks[PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED], ++ PA_HOOK_NORMAL, client_proplist_changed_cb, stream); ++ ++ return stream; ++} ++ ++static void stream_put(struct stream *stream) { ++ pa_proplist *proplist = NULL; ++ ++ pa_assert(stream); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ proplist = stream->sink_input->proplist; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ proplist = stream->source_output->proplist; ++ break; ++ } ++ ++ pas_stream_put(stream->stream, proplist); ++} ++ ++static void stream_unlink(struct stream *stream) { ++ pa_assert(stream); ++ ++ if (stream->unlinked) ++ return; ++ ++ stream->unlinked = true; ++ ++ if (stream->stream) ++ pas_stream_unlink(stream->stream); ++} ++ ++static void stream_free(struct stream *stream) { ++ pa_assert(stream); ++ ++ if (!stream->unlinked) ++ stream_unlink(stream); ++ ++ if (stream->client_proplist_changed_slot) ++ pa_hook_slot_free(stream->client_proplist_changed_slot); ++ ++ if (stream->proplist_changed_slot) ++ pa_hook_slot_free(stream->proplist_changed_slot); ++ ++ if (stream->stream) ++ pas_stream_free(stream->stream); ++ ++ pa_xfree(stream); ++} ++ ++static void create_stream(pa_stream_creator *creator, enum stream_type type, void *core_stream) { ++ struct stream *stream; ++ ++ pa_assert(creator); ++ pa_assert(core_stream); ++ ++ stream = stream_new(creator, type, core_stream); ++ pa_hashmap_put(creator->streams, core_stream, stream); ++ stream_put(stream); ++} ++ ++static pa_hook_result_t sink_input_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_stream_creator *creator = userdata; ++ pa_sink_input *input = call_data; ++ ++ pa_assert(creator); ++ pa_assert(input); ++ ++ create_stream(creator, STREAM_TYPE_SINK_INPUT, input); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t sink_input_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_stream_creator *creator = userdata; ++ pa_sink_input *input = call_data; ++ ++ pa_assert(creator); ++ pa_assert(input); ++ ++ pa_hashmap_remove_and_free(creator->streams, input); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t source_output_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_stream_creator *creator = userdata; ++ pa_source_output *output = call_data; ++ ++ pa_assert(creator); ++ pa_assert(output); ++ ++ create_stream(creator, STREAM_TYPE_SOURCE_OUTPUT, output); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t source_output_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_stream_creator *creator = userdata; ++ pa_source_output *output = call_data; ++ ++ pa_assert(creator); ++ pa_assert(output); ++ ++ pa_hashmap_remove_and_free(creator->streams, output); ++ ++ return PA_HOOK_OK; ++} ++ ++pa_stream_creator *pa_stream_creator_new(pa_volume_api *api) { ++ pa_stream_creator *creator; ++ uint32_t idx; ++ pa_sink_input *input; ++ pa_source_output *output; ++ ++ pa_assert(api); ++ ++ creator = pa_xnew0(pa_stream_creator, 1); ++ creator->volume_api = api; ++ creator->streams = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) stream_free); ++ creator->sink_input_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_NORMAL, ++ sink_input_put_cb, creator); ++ creator->sink_input_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_NORMAL, ++ sink_input_unlink_cb, creator); ++ creator->source_output_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_NORMAL, ++ source_output_put_cb, creator); ++ creator->source_output_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_NORMAL, ++ source_output_unlink_cb, creator); ++ ++ PA_IDXSET_FOREACH(input, api->core->sink_inputs, idx) ++ create_stream(creator, STREAM_TYPE_SINK_INPUT, input); ++ ++ PA_IDXSET_FOREACH(output, api->core->source_outputs, idx) ++ create_stream(creator, STREAM_TYPE_SOURCE_OUTPUT, output); ++ ++ return creator; ++} ++ ++void pa_stream_creator_free(pa_stream_creator *creator) { ++ pa_assert(creator); ++ ++ if (creator->streams) ++ pa_hashmap_remove_all(creator->streams); ++ ++ if (creator->source_output_unlink_slot) ++ pa_hook_slot_free(creator->source_output_unlink_slot); ++ ++ if (creator->source_output_put_slot) ++ pa_hook_slot_free(creator->source_output_put_slot); ++ ++ if (creator->sink_input_unlink_slot) ++ pa_hook_slot_free(creator->sink_input_unlink_slot); ++ ++ if (creator->sink_input_put_slot) ++ pa_hook_slot_free(creator->sink_input_put_slot); ++ ++ if (creator->streams) ++ pa_hashmap_free(creator->streams); ++ ++ pa_xfree(creator); ++} +diff --git a/src/modules/volume-api/stream-creator.h b/src/modules/volume-api/stream-creator.h +new file mode 100644 +index 0000000..97a03a4 +--- /dev/null ++++ b/src/modules/volume-api/stream-creator.h +@@ -0,0 +1,32 @@ ++#ifndef foostreamcreatorhfoo ++#define foostreamcreatorhfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++ ++typedef struct pa_stream_creator pa_stream_creator; ++ ++pa_stream_creator *pa_stream_creator_new(pa_volume_api *api); ++void pa_stream_creator_free(pa_stream_creator *creator); ++ ++#endif +diff --git a/src/modules/volume-api/volume-api.c b/src/modules/volume-api/volume-api.c +new file mode 100644 +index 0000000..9abea7e +--- /dev/null ++++ b/src/modules/volume-api/volume-api.c +@@ -0,0 +1,647 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "volume-api.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++static pa_volume_api *volume_api_new(pa_core *core); ++static void volume_api_free(pa_volume_api *api); ++ ++pa_volume_api *pa_volume_api_get(pa_core *core) { ++ pa_volume_api *api; ++ ++ pa_assert(core); ++ ++ api = pa_shared_get(core, "volume-api"); ++ ++ if (api) ++ pa_volume_api_ref(api); ++ else { ++ api = volume_api_new(core); ++ pa_assert_se(pa_shared_set(core, "volume-api", api) >= 0); ++ } ++ ++ return api; ++} ++ ++pa_volume_api *pa_volume_api_ref(pa_volume_api *api) { ++ pa_assert(api); ++ ++ api->refcnt++; ++ ++ return api; ++} ++ ++void pa_volume_api_unref(pa_volume_api *api) { ++ pa_assert(api); ++ pa_assert(api->refcnt > 0); ++ ++ api->refcnt--; ++ ++ if (api->refcnt == 0) { ++ pa_assert_se(pa_shared_remove(api->core, "volume-api") >= 0); ++ volume_api_free(api); ++ } ++} ++ ++void pa_volume_api_add_binding_target_type(pa_volume_api *api, pa_binding_target_type *type) { ++ pa_assert(api); ++ pa_assert(type); ++ ++ pa_assert_se(pa_hashmap_put(api->binding_target_types, type->name, type) >= 0); ++ ++ pa_log_debug("Added binding target type %s.", type->name); ++ ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_ADDED], type); ++} ++ ++void pa_volume_api_remove_binding_target_type(pa_volume_api *api, pa_binding_target_type *type) { ++ pa_assert(api); ++ pa_assert(type); ++ ++ pa_log_debug("Removing binding target type %s.", type->name); ++ ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_REMOVED], type); ++ ++ pa_assert_se(pa_hashmap_remove(api->binding_target_types, type->name)); ++} ++ ++static void create_builtin_binding_target_types(pa_volume_api *api) { ++ pa_binding_target_type *type; ++ ++ pa_assert(api); ++ ++ type = pa_audio_group_create_binding_target_type(api); ++ pa_volume_api_add_binding_target_type(api, type); ++} ++ ++static void delete_builtin_binding_target_types(pa_volume_api *api) { ++ pa_binding_target_type *type; ++ ++ pa_assert(api); ++ ++ type = pa_hashmap_get(api->binding_target_types, PA_AUDIO_GROUP_BINDING_TARGET_TYPE); ++ pa_volume_api_remove_binding_target_type(api, type); ++} ++ ++static void create_objects_defer_event_cb(pa_mainloop_api *mainloop_api, pa_defer_event *event, void *userdata) { ++ pa_volume_api *volume_api = userdata; ++ ++ pa_assert(volume_api); ++ pa_assert(event == volume_api->create_objects_defer_event); ++ ++ mainloop_api->defer_free(event); ++ volume_api->create_objects_defer_event = NULL; ++ ++ volume_api->device_creator = pa_device_creator_new(volume_api); ++ volume_api->stream_creator = pa_stream_creator_new(volume_api); ++} ++ ++static pa_volume_api *volume_api_new(pa_core *core) { ++ pa_volume_api *api; ++ unsigned i; ++ ++ pa_assert(core); ++ ++ api = pa_xnew0(pa_volume_api, 1); ++ api->core = core; ++ api->refcnt = 1; ++ api->binding_target_types = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ api->names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree); ++ api->volume_controls = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ api->mute_controls = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ api->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ api->streams = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ api->audio_groups = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ ++ for (i = 0; i < PA_VOLUME_API_HOOK_MAX; i++) ++ pa_hook_init(&api->hooks[i], api); ++ ++ create_builtin_binding_target_types(api); ++ ++ /* We delay the object creation to ensure that policy modules have a chance ++ * to affect the initialization of the objects. If we created the objects ++ * immediately, policy modules wouldn't have a chance of connecting to the ++ * object creation hooks before the objects are created. */ ++ api->create_objects_defer_event = core->mainloop->defer_new(core->mainloop, create_objects_defer_event_cb, api); ++ ++ pa_log_debug("Created a pa_volume_api object."); ++ ++ return api; ++} ++ ++static void volume_api_free(pa_volume_api *api) { ++ unsigned i; ++ ++ pa_assert(api); ++ pa_assert(api->refcnt == 0); ++ ++ pa_log_debug("Freeing the pa_volume_api object."); ++ ++ if (api->stream_creator) ++ pa_stream_creator_free(api->stream_creator); ++ ++ if (api->device_creator) ++ pa_device_creator_free(api->device_creator); ++ ++ if (api->create_objects_defer_event) ++ api->core->mainloop->defer_free(api->create_objects_defer_event); ++ ++ if (api->binding_target_types) ++ delete_builtin_binding_target_types(api); ++ ++ for (i = 0; i < PA_VOLUME_API_HOOK_MAX; i++) ++ pa_hook_done(&api->hooks[i]); ++ ++ if (api->audio_groups) { ++ pa_assert(pa_hashmap_isempty(api->audio_groups)); ++ pa_hashmap_free(api->audio_groups); ++ } ++ ++ if (api->streams) { ++ pa_assert(pa_hashmap_isempty(api->streams)); ++ pa_hashmap_free(api->streams); ++ } ++ ++ if (api->devices) { ++ pa_assert(pa_hashmap_isempty(api->devices)); ++ pa_hashmap_free(api->devices); ++ } ++ ++ if (api->mute_controls) { ++ pa_assert(pa_hashmap_isempty(api->mute_controls)); ++ pa_hashmap_free(api->mute_controls); ++ } ++ ++ if (api->volume_controls) { ++ pa_assert(pa_hashmap_isempty(api->volume_controls)); ++ pa_hashmap_free(api->volume_controls); ++ } ++ ++ if (api->names) { ++ pa_assert(pa_hashmap_isempty(api->names)); ++ pa_hashmap_free(api->names); ++ } ++ ++ if (api->binding_target_types) { ++ pa_assert(pa_hashmap_isempty(api->binding_target_types)); ++ pa_hashmap_free(api->binding_target_types); ++ } ++ ++ pa_xfree(api); ++} ++ ++int pa_volume_api_register_name(pa_volume_api *api, const char *requested_name, bool fail_if_already_registered, ++ const char **registered_name) { ++ char *n; ++ ++ pa_assert(api); ++ pa_assert(requested_name); ++ pa_assert(registered_name); ++ ++ n = pa_xstrdup(requested_name); ++ ++ if (pa_hashmap_put(api->names, n, n) < 0) { ++ unsigned i = 1; ++ ++ pa_xfree(n); ++ ++ if (fail_if_already_registered) { ++ pa_log("Name %s already registered.", requested_name); ++ return -PA_ERR_EXIST; ++ } ++ ++ do { ++ i++; ++ n = pa_sprintf_malloc("%s.%u", requested_name, i); ++ } while (pa_hashmap_put(api->names, n, n) < 0); ++ } ++ ++ *registered_name = n; ++ ++ return 0; ++} ++ ++void pa_volume_api_unregister_name(pa_volume_api *api, const char *name) { ++ pa_assert(api); ++ pa_assert(name); ++ ++ pa_assert_se(pa_hashmap_remove_and_free(api->names, name) >= 0); ++} ++ ++uint32_t pa_volume_api_allocate_volume_control_index(pa_volume_api *api) { ++ uint32_t idx; ++ ++ pa_assert(api); ++ ++ idx = api->next_volume_control_index++; ++ ++ return idx; ++} ++ ++static void set_main_output_volume_control_internal(pa_volume_api *api, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(api); ++ ++ old_control = api->main_output_volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ api->main_output_volume_control = control; ++ pa_log_debug("Main output volume control changed from %s to %s.", old_control ? old_control->name : "(unset)", ++ control ? control->name : "(unset)"); ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED], api); ++} ++ ++static void set_main_input_volume_control_internal(pa_volume_api *api, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(api); ++ ++ old_control = api->main_input_volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ api->main_input_volume_control = control; ++ pa_log_debug("Main input volume control changed from %s to %s.", old_control ? old_control->name : "(unset)", ++ control ? control->name : "(unset)"); ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_INPUT_VOLUME_CONTROL_CHANGED], api); ++} ++ ++void pa_volume_api_add_volume_control(pa_volume_api *api, pa_volume_control *control) { ++ pa_assert(api); ++ pa_assert(control); ++ ++ pa_assert_se(pa_hashmap_put(api->volume_controls, (void *) control->name, control) >= 0); ++} ++ ++int pa_volume_api_remove_volume_control(pa_volume_api *api, pa_volume_control *control) { ++ pa_assert(api); ++ pa_assert(control); ++ ++ if (!pa_hashmap_remove(api->volume_controls, control->name)) ++ return -1; ++ ++ if (control == api->main_output_volume_control) ++ set_main_output_volume_control_internal(api, NULL); ++ ++ if (control == api->main_input_volume_control) ++ set_main_input_volume_control_internal(api, NULL); ++ ++ return 0; ++} ++ ++pa_volume_control *pa_volume_api_get_volume_control_by_index(pa_volume_api *api, uint32_t idx) { ++ pa_volume_control *control; ++ void *state; ++ ++ pa_assert(api); ++ ++ PA_HASHMAP_FOREACH(control, api->volume_controls, state) { ++ if (control->index == idx) ++ return control; ++ } ++ ++ return NULL; ++} ++ ++uint32_t pa_volume_api_allocate_mute_control_index(pa_volume_api *api) { ++ uint32_t idx; ++ ++ pa_assert(api); ++ ++ idx = api->next_mute_control_index++; ++ ++ return idx; ++} ++ ++static void set_main_output_mute_control_internal(pa_volume_api *api, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(api); ++ ++ old_control = api->main_output_mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ api->main_output_mute_control = control; ++ pa_log_debug("Main output mute control changed from %s to %s.", old_control ? old_control->name : "(unset)", ++ control ? control->name : "(unset)"); ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_OUTPUT_MUTE_CONTROL_CHANGED], api); ++} ++ ++static void set_main_input_mute_control_internal(pa_volume_api *api, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(api); ++ ++ old_control = api->main_input_mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ api->main_input_mute_control = control; ++ pa_log_debug("Main input mute control changed from %s to %s.", old_control ? old_control->name : "(unset)", ++ control ? control->name : "(unset)"); ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_INPUT_MUTE_CONTROL_CHANGED], api); ++} ++ ++void pa_volume_api_add_mute_control(pa_volume_api *api, pa_mute_control *control) { ++ pa_assert(api); ++ pa_assert(control); ++ ++ pa_assert_se(pa_hashmap_put(api->mute_controls, (void *) control->name, control) >= 0); ++} ++ ++int pa_volume_api_remove_mute_control(pa_volume_api *api, pa_mute_control *control) { ++ pa_assert(api); ++ pa_assert(control); ++ ++ if (!pa_hashmap_remove(api->mute_controls, control->name)) ++ return -1; ++ ++ if (control == api->main_output_mute_control) ++ set_main_output_mute_control_internal(api, NULL); ++ ++ if (control == api->main_input_mute_control) ++ set_main_input_mute_control_internal(api, NULL); ++ ++ return 0; ++} ++ ++pa_mute_control *pa_volume_api_get_mute_control_by_index(pa_volume_api *api, uint32_t idx) { ++ pa_mute_control *control; ++ void *state; ++ ++ pa_assert(api); ++ ++ PA_HASHMAP_FOREACH(control, api->mute_controls, state) { ++ if (control->index == idx) ++ return control; ++ } ++ ++ return NULL; ++} ++ ++uint32_t pa_volume_api_allocate_device_index(pa_volume_api *api) { ++ uint32_t idx; ++ ++ pa_assert(api); ++ ++ idx = api->next_device_index++; ++ ++ return idx; ++} ++ ++void pa_volume_api_add_device(pa_volume_api *api, pa_device *device) { ++ pa_assert(api); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_put(api->devices, (void *) device->name, device) >= 0); ++} ++ ++int pa_volume_api_remove_device(pa_volume_api *api, pa_device *device) { ++ pa_assert(api); ++ pa_assert(device); ++ ++ if (!pa_hashmap_remove(api->devices, device->name)) ++ return -1; ++ ++ return 0; ++} ++ ++pa_device *pa_volume_api_get_device_by_index(pa_volume_api *api, uint32_t idx) { ++ pa_device *device; ++ void *state; ++ ++ pa_assert(api); ++ ++ PA_HASHMAP_FOREACH(device, api->devices, state) { ++ if (device->index == idx) ++ return device; ++ } ++ ++ return NULL; ++} ++ ++uint32_t pa_volume_api_allocate_stream_index(pa_volume_api *api) { ++ uint32_t idx; ++ ++ pa_assert(api); ++ ++ idx = api->next_stream_index++; ++ ++ return idx; ++} ++ ++void pa_volume_api_add_stream(pa_volume_api *api, pas_stream *stream) { ++ pa_assert(api); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_put(api->streams, (void *) stream->name, stream) >= 0); ++} ++ ++int pa_volume_api_remove_stream(pa_volume_api *api, pas_stream *stream) { ++ pa_assert(api); ++ pa_assert(stream); ++ ++ if (!pa_hashmap_remove(api->streams, stream->name)) ++ return -1; ++ ++ return 0; ++} ++ ++pas_stream *pa_volume_api_get_stream_by_index(pa_volume_api *api, uint32_t idx) { ++ pas_stream *stream; ++ void *state; ++ ++ pa_assert(api); ++ ++ PA_HASHMAP_FOREACH(stream, api->streams, state) { ++ if (stream->index == idx) ++ return stream; ++ } ++ ++ return NULL; ++} ++ ++uint32_t pa_volume_api_allocate_audio_group_index(pa_volume_api *api) { ++ uint32_t idx; ++ ++ pa_assert(api); ++ ++ idx = api->next_audio_group_index++; ++ ++ return idx; ++} ++ ++void pa_volume_api_add_audio_group(pa_volume_api *api, pa_audio_group *group) { ++ pa_assert(api); ++ pa_assert(group); ++ ++ pa_assert_se(pa_hashmap_put(api->audio_groups, (void *) group->name, group) >= 0); ++} ++ ++int pa_volume_api_remove_audio_group(pa_volume_api *api, pa_audio_group *group) { ++ pa_assert(api); ++ pa_assert(group); ++ ++ if (!pa_hashmap_remove(api->audio_groups, group->name)) ++ return -1; ++ ++ return 0; ++} ++ ++pa_audio_group *pa_volume_api_get_audio_group_by_index(pa_volume_api *api, uint32_t idx) { ++ pa_audio_group *group; ++ void *state; ++ ++ pa_assert(api); ++ ++ PA_HASHMAP_FOREACH(group, api->audio_groups, state) { ++ if (group->index == idx) ++ return group; ++ } ++ ++ return NULL; ++} ++ ++void pa_volume_api_set_main_output_volume_control(pa_volume_api *api, pa_volume_control *control) { ++ pa_assert(api); ++ ++ if (api->main_output_volume_control_binding) { ++ pa_binding_free(api->main_output_volume_control_binding); ++ api->main_output_volume_control_binding = NULL; ++ } ++ ++ set_main_output_volume_control_internal(api, control); ++} ++ ++void pa_volume_api_set_main_input_volume_control(pa_volume_api *api, pa_volume_control *control) { ++ pa_assert(api); ++ ++ if (api->main_input_volume_control_binding) { ++ pa_binding_free(api->main_input_volume_control_binding); ++ api->main_input_volume_control_binding = NULL; ++ } ++ ++ set_main_input_volume_control_internal(api, control); ++} ++ ++void pa_volume_api_set_main_output_mute_control(pa_volume_api *api, pa_mute_control *control) { ++ pa_assert(api); ++ ++ if (api->main_output_mute_control_binding) { ++ pa_binding_free(api->main_output_mute_control_binding); ++ api->main_output_mute_control_binding = NULL; ++ } ++ ++ set_main_output_mute_control_internal(api, control); ++} ++ ++void pa_volume_api_set_main_input_mute_control(pa_volume_api *api, pa_mute_control *control) { ++ pa_assert(api); ++ ++ if (api->main_input_mute_control_binding) { ++ pa_binding_free(api->main_input_mute_control_binding); ++ api->main_input_mute_control_binding = NULL; ++ } ++ ++ set_main_input_mute_control_internal(api, control); ++} ++ ++void pa_volume_api_bind_main_output_volume_control(pa_volume_api *api, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = api, ++ .set_value = (pa_binding_set_value_cb_t) set_main_output_volume_control_internal, ++ }; ++ ++ pa_assert(api); ++ pa_assert(target_info); ++ ++ if (api->main_output_volume_control_binding) ++ pa_binding_free(api->main_output_volume_control_binding); ++ ++ api->main_output_volume_control_binding = pa_binding_new(api, &owner_info, target_info); ++} ++ ++void pa_volume_api_bind_main_input_volume_control(pa_volume_api *api, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = api, ++ .set_value = (pa_binding_set_value_cb_t) set_main_input_volume_control_internal, ++ }; ++ ++ pa_assert(api); ++ pa_assert(target_info); ++ ++ if (api->main_input_volume_control_binding) ++ pa_binding_free(api->main_input_volume_control_binding); ++ ++ api->main_input_volume_control_binding = pa_binding_new(api, &owner_info, target_info); ++} ++ ++void pa_volume_api_bind_main_output_mute_control(pa_volume_api *api, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = api, ++ .set_value = (pa_binding_set_value_cb_t) set_main_output_mute_control_internal, ++ }; ++ ++ pa_assert(api); ++ pa_assert(target_info); ++ ++ if (api->main_output_mute_control_binding) ++ pa_binding_free(api->main_output_mute_control_binding); ++ ++ api->main_output_mute_control_binding = pa_binding_new(api, &owner_info, target_info); ++} ++ ++void pa_volume_api_bind_main_input_mute_control(pa_volume_api *api, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = api, ++ .set_value = (pa_binding_set_value_cb_t) set_main_input_mute_control_internal, ++ }; ++ ++ pa_assert(api); ++ pa_assert(target_info); ++ ++ if (api->main_input_mute_control_binding) ++ pa_binding_free(api->main_input_mute_control_binding); ++ ++ api->main_input_mute_control_binding = pa_binding_new(api, &owner_info, target_info); ++} +diff --git a/src/modules/volume-api/volume-api.h b/src/modules/volume-api/volume-api.h +new file mode 100644 +index 0000000..73a1410 +--- /dev/null ++++ b/src/modules/volume-api/volume-api.h +@@ -0,0 +1,163 @@ ++#ifndef foovolumeapihfoo ++#define foovolumeapihfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++ ++typedef struct pa_volume_api pa_volume_api; ++ ++/* Avoid circular dependencies... */ ++typedef struct pa_audio_group pa_audio_group; ++typedef struct pa_binding pa_binding; ++typedef struct pa_binding_target_info pa_binding_target_info; ++typedef struct pa_binding_target_type pa_binding_target_type; ++typedef struct pa_device pa_device; ++typedef struct pa_device_creator pa_device_creator; ++typedef struct pa_mute_control pa_mute_control; ++typedef struct pas_stream pas_stream; ++typedef struct pa_stream_creator pa_stream_creator; ++typedef struct pa_volume_control pa_volume_control; ++ ++enum { ++ PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_ADDED, ++ PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_REMOVED, ++ PA_VOLUME_API_HOOK_VOLUME_CONTROL_PUT, ++ PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK, ++ PA_VOLUME_API_HOOK_VOLUME_CONTROL_DESCRIPTION_CHANGED, ++ PA_VOLUME_API_HOOK_VOLUME_CONTROL_VOLUME_CHANGED, ++ PA_VOLUME_API_HOOK_MUTE_CONTROL_PUT, ++ PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK, ++ PA_VOLUME_API_HOOK_MUTE_CONTROL_DESCRIPTION_CHANGED, ++ PA_VOLUME_API_HOOK_MUTE_CONTROL_MUTE_CHANGED, ++ PA_VOLUME_API_HOOK_DEVICE_PUT, ++ PA_VOLUME_API_HOOK_DEVICE_UNLINK, ++ PA_VOLUME_API_HOOK_DEVICE_DESCRIPTION_CHANGED, ++ PA_VOLUME_API_HOOK_DEVICE_VOLUME_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_DEVICE_MUTE_CONTROL_CHANGED, ++ ++ /* Policy modules can use this to set the initial volume control for a ++ * stream. The hook callback should use pas_stream_set_volume_control() to ++ * set the volume control. The hook callback should not do anything if ++ * stream->volume_control is already non-NULL. */ ++ PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_VOLUME_CONTROL, ++ ++ /* Policy modules can use this to set the initial mute control for a ++ * stream. The hook callback should use pas_stream_set_mute_control() to ++ * set the mute control. The hook callback should not do anything if ++ * stream->mute_control is already non-NULL. */ ++ PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_MUTE_CONTROL, ++ ++ PA_VOLUME_API_HOOK_STREAM_PUT, ++ PA_VOLUME_API_HOOK_STREAM_UNLINK, ++ PA_VOLUME_API_HOOK_STREAM_DESCRIPTION_CHANGED, ++ PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_STREAM_MUTE_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_AUDIO_GROUP_PUT, ++ PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK, ++ PA_VOLUME_API_HOOK_AUDIO_GROUP_VOLUME_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_AUDIO_GROUP_MUTE_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_MAIN_INPUT_VOLUME_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_MAIN_OUTPUT_MUTE_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_MAIN_INPUT_MUTE_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_MAX ++}; ++ ++struct pa_volume_api { ++ pa_core *core; ++ unsigned refcnt; ++ pa_hashmap *binding_target_types; /* name -> pa_binding_target_type */ ++ pa_hashmap *names; /* object name -> object name (hashmap-as-a-set) */ ++ pa_hashmap *volume_controls; /* name -> pa_volume_control */ ++ pa_hashmap *mute_controls; /* name -> pa_mute_control */ ++ pa_hashmap *devices; /* name -> pa_device */ ++ pa_hashmap *streams; /* name -> pas_stream */ ++ pa_hashmap *audio_groups; /* name -> pa_audio_group */ ++ pa_volume_control *main_output_volume_control; ++ pa_volume_control *main_input_volume_control; ++ pa_mute_control *main_output_mute_control; ++ pa_mute_control *main_input_mute_control; ++ ++ uint32_t next_volume_control_index; ++ uint32_t next_mute_control_index; ++ uint32_t next_device_index; ++ uint32_t next_stream_index; ++ uint32_t next_audio_group_index; ++ pa_binding *main_output_volume_control_binding; ++ pa_binding *main_input_volume_control_binding; ++ pa_binding *main_output_mute_control_binding; ++ pa_binding *main_input_mute_control_binding; ++ pa_hook hooks[PA_VOLUME_API_HOOK_MAX]; ++ pa_defer_event *create_objects_defer_event; ++ pa_device_creator *device_creator; ++ pa_stream_creator *stream_creator; ++}; ++ ++pa_volume_api *pa_volume_api_get(pa_core *core); ++pa_volume_api *pa_volume_api_ref(pa_volume_api *api); ++void pa_volume_api_unref(pa_volume_api *api); ++ ++void pa_volume_api_add_binding_target_type(pa_volume_api *api, pa_binding_target_type *type); ++void pa_volume_api_remove_binding_target_type(pa_volume_api *api, pa_binding_target_type *type); ++ ++/* If fail_if_already_registered is false, this function never fails. */ ++int pa_volume_api_register_name(pa_volume_api *api, const char *requested_name, bool fail_if_already_registered, ++ const char **registered_name); ++ ++void pa_volume_api_unregister_name(pa_volume_api *api, const char *name); ++ ++uint32_t pa_volume_api_allocate_volume_control_index(pa_volume_api *api); ++void pa_volume_api_add_volume_control(pa_volume_api *api, pa_volume_control *control); ++int pa_volume_api_remove_volume_control(pa_volume_api *api, pa_volume_control *control); ++pa_volume_control *pa_volume_api_get_volume_control_by_index(pa_volume_api *api, uint32_t idx); ++ ++uint32_t pa_volume_api_allocate_mute_control_index(pa_volume_api *api); ++void pa_volume_api_add_mute_control(pa_volume_api *api, pa_mute_control *control); ++int pa_volume_api_remove_mute_control(pa_volume_api *api, pa_mute_control *control); ++pa_mute_control *pa_volume_api_get_mute_control_by_index(pa_volume_api *api, uint32_t idx); ++ ++uint32_t pa_volume_api_allocate_device_index(pa_volume_api *api); ++void pa_volume_api_add_device(pa_volume_api *api, pa_device *device); ++int pa_volume_api_remove_device(pa_volume_api *api, pa_device *device); ++pa_device *pa_volume_api_get_device_by_index(pa_volume_api *api, uint32_t idx); ++ ++uint32_t pa_volume_api_allocate_stream_index(pa_volume_api *api); ++void pa_volume_api_add_stream(pa_volume_api *api, pas_stream *stream); ++int pa_volume_api_remove_stream(pa_volume_api *api, pas_stream *stream); ++pas_stream *pa_volume_api_get_stream_by_index(pa_volume_api *api, uint32_t idx); ++ ++uint32_t pa_volume_api_allocate_audio_group_index(pa_volume_api *api); ++void pa_volume_api_add_audio_group(pa_volume_api *api, pa_audio_group *group); ++int pa_volume_api_remove_audio_group(pa_volume_api *api, pa_audio_group *group); ++pa_audio_group *pa_volume_api_get_audio_group_by_index(pa_volume_api *api, uint32_t idx); ++ ++void pa_volume_api_set_main_output_volume_control(pa_volume_api *api, pa_volume_control *control); ++void pa_volume_api_set_main_input_volume_control(pa_volume_api *api, pa_volume_control *control); ++void pa_volume_api_set_main_output_mute_control(pa_volume_api *api, pa_mute_control *control); ++void pa_volume_api_set_main_input_mute_control(pa_volume_api *api, pa_mute_control *control); ++void pa_volume_api_bind_main_output_volume_control(pa_volume_api *api, pa_binding_target_info *target_info); ++void pa_volume_api_bind_main_input_volume_control(pa_volume_api *api, pa_binding_target_info *target_info); ++void pa_volume_api_bind_main_output_mute_control(pa_volume_api *api, pa_binding_target_info *target_info); ++void pa_volume_api_bind_main_input_mute_control(pa_volume_api *api, pa_binding_target_info *target_info); ++ ++#endif +diff --git a/src/modules/volume-api/volume-control.c b/src/modules/volume-api/volume-control.c +new file mode 100644 +index 0000000..c7f5dbb +--- /dev/null ++++ b/src/modules/volume-api/volume-control.c +@@ -0,0 +1,363 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "volume-control.h" ++ ++#include ++#include ++#include ++ ++#include ++ ++pa_volume_control *pa_volume_control_new(pa_volume_api *api, const char *name, const char *description, bool convertible_to_dB, ++ bool channel_map_is_writable) { ++ pa_volume_control *control; ++ ++ pa_assert(api); ++ pa_assert(name); ++ pa_assert(description); ++ ++ control = pa_xnew0(pa_volume_control, 1); ++ control->volume_api = api; ++ control->index = pa_volume_api_allocate_volume_control_index(api); ++ pa_assert_se(pa_volume_api_register_name(api, name, false, &control->name) >= 0); ++ control->description = pa_xstrdup(description); ++ control->proplist = pa_proplist_new(); ++ pa_bvolume_init_invalid(&control->volume); ++ control->convertible_to_dB = convertible_to_dB; ++ control->channel_map_is_writable = channel_map_is_writable; ++ control->devices = pa_hashmap_new(NULL, NULL); ++ control->default_for_devices = pa_hashmap_new(NULL, NULL); ++ control->streams = pa_hashmap_new(NULL, NULL); ++ control->audio_groups = pa_hashmap_new(NULL, NULL); ++ ++ return control; ++} ++ ++void pa_volume_control_put(pa_volume_control *control, const pa_bvolume *initial_volume, ++ pa_volume_control_set_initial_volume_cb_t set_initial_volume_cb) { ++ const char *prop_key; ++ void *state = NULL; ++ char volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX]; ++ char balance_str[PA_BVOLUME_SNPRINT_BALANCE_MAX]; ++ ++ pa_assert(control); ++ pa_assert((initial_volume && pa_bvolume_valid(initial_volume, true, true)) || control->set_volume); ++ pa_assert((initial_volume && pa_channel_map_valid(&initial_volume->channel_map)) || control->channel_map_is_writable); ++ pa_assert(set_initial_volume_cb || !control->set_volume); ++ ++ if (initial_volume && pa_bvolume_valid(initial_volume, true, false)) ++ control->volume.volume = initial_volume->volume; ++ else ++ control->volume.volume = PA_VOLUME_NORM / 3; ++ ++ if (initial_volume && pa_bvolume_valid(initial_volume, false, true)) ++ pa_bvolume_copy_balance(&control->volume, initial_volume); ++ else if (initial_volume && pa_channel_map_valid(&initial_volume->channel_map)) ++ pa_bvolume_reset_balance(&control->volume, &initial_volume->channel_map); ++ else { ++ pa_channel_map_init_mono(&control->volume.channel_map); ++ pa_bvolume_reset_balance(&control->volume, &control->volume.channel_map); ++ } ++ ++ if (set_initial_volume_cb) ++ set_initial_volume_cb(control); ++ ++ pa_volume_api_add_volume_control(control->volume_api, control); ++ ++ control->linked = true; ++ ++ pa_log_debug("Created volume control #%u.", control->index); ++ pa_log_debug(" Name: %s", control->name); ++ pa_log_debug(" Description: %s", control->description); ++ pa_log_debug(" Properties:"); ++ ++ while ((prop_key = pa_proplist_iterate(control->proplist, &state))) ++ pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(control->proplist, prop_key))); ++ ++ pa_log_debug(" Volume: %s", pa_volume_snprint_verbose(volume_str, sizeof(volume_str), control->volume.volume, ++ control->convertible_to_dB)); ++ pa_log_debug(" Balance: %s", pa_bvolume_snprint_balance(balance_str, sizeof(balance_str), &control->volume)); ++ pa_log_debug(" Channel map is writable: %s", pa_yes_no(control->channel_map_is_writable)); ++ ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_PUT], control); ++} ++ ++void pa_volume_control_unlink(pa_volume_control *control) { ++ pa_audio_group *group; ++ pa_device *device; ++ pas_stream *stream; ++ ++ pa_assert(control); ++ ++ if (control->unlinked) { ++ pa_log_debug("Unlinking volume control %s (already unlinked, this is a no-op).", control->name); ++ return; ++ } ++ ++ control->unlinked = true; ++ ++ pa_log_debug("Unlinking volume control %s.", control->name); ++ ++ if (control->linked) ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK], control); ++ ++ pa_volume_api_remove_volume_control(control->volume_api, control); ++ ++ while ((group = pa_hashmap_first(control->audio_groups))) ++ pa_audio_group_set_volume_control(group, NULL); ++ ++ while ((stream = pa_hashmap_first(control->streams))) ++ pas_stream_set_volume_control(stream, NULL); ++ ++ while ((device = pa_hashmap_first(control->default_for_devices))) ++ pa_device_set_default_volume_control(device, NULL); ++ ++ while ((device = pa_hashmap_first(control->devices))) { ++ /* Why do we have this assertion here? The concern is that if we call ++ * pa_device_set_volume_control() for some device that has the ++ * use_default_volume_control flag set, then that flag will be unset as ++ * a side effect, and we don't want that side effect. This assertion ++ * should be safe, because we just called ++ * pa_device_set_default_volume_control(NULL) for each device that this ++ * control was the default for, and that should ensure that we don't ++ * any more hold any references to devices that used to use this ++ * control as the default. */ ++ pa_assert(!device->use_default_volume_control); ++ pa_device_set_volume_control(device, NULL); ++ } ++} ++ ++void pa_volume_control_free(pa_volume_control *control) { ++ pa_assert(control); ++ ++ if (!control->unlinked) ++ pa_volume_control_unlink(control); ++ ++ if (control->audio_groups) { ++ pa_assert(pa_hashmap_isempty(control->audio_groups)); ++ pa_hashmap_free(control->audio_groups); ++ } ++ ++ if (control->streams) { ++ pa_assert(pa_hashmap_isempty(control->streams)); ++ pa_hashmap_free(control->streams); ++ } ++ ++ if (control->default_for_devices) { ++ pa_assert(pa_hashmap_isempty(control->default_for_devices)); ++ pa_hashmap_free(control->default_for_devices); ++ } ++ ++ if (control->devices) { ++ pa_assert(pa_hashmap_isempty(control->devices)); ++ pa_hashmap_free(control->devices); ++ } ++ ++ if (control->proplist) ++ pa_proplist_free(control->proplist); ++ ++ pa_xfree(control->description); ++ ++ if (control->name) ++ pa_volume_api_unregister_name(control->volume_api, control->name); ++ ++ pa_xfree(control); ++} ++ ++void pa_volume_control_set_owner_audio_group(pa_volume_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ control->owner_audio_group = group; ++} ++ ++static void set_volume_internal(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance) { ++ pa_bvolume old_volume; ++ bool volume_changed; ++ bool balance_changed; ++ ++ pa_assert(control); ++ pa_assert(volume); ++ ++ old_volume = control->volume; ++ volume_changed = !pa_bvolume_equal(volume, &old_volume, set_volume, false); ++ balance_changed = !pa_bvolume_equal(volume, &old_volume, false, set_balance); ++ ++ if (!volume_changed && !balance_changed) ++ return; ++ ++ if (volume_changed) ++ control->volume.volume = volume->volume; ++ ++ if (balance_changed) ++ pa_bvolume_copy_balance(&control->volume, volume); ++ ++ if (!control->linked || control->unlinked) ++ return; ++ ++ if (volume_changed) { ++ char old_volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX]; ++ char new_volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX]; ++ ++ pa_log_debug("The volume of volume control %s changed from %s to %s.", control->name, ++ pa_volume_snprint_verbose(old_volume_str, sizeof(old_volume_str), old_volume.volume, ++ control->convertible_to_dB), ++ pa_volume_snprint_verbose(new_volume_str, sizeof(new_volume_str), control->volume.volume, ++ control->convertible_to_dB)); ++ } ++ ++ if (balance_changed) { ++ char old_balance_str[PA_BVOLUME_SNPRINT_BALANCE_MAX]; ++ char new_balance_str[PA_BVOLUME_SNPRINT_BALANCE_MAX]; ++ ++ pa_log_debug("The balance of volume control %s changed from %s to %s.", control->name, ++ pa_bvolume_snprint_balance(old_balance_str, sizeof(old_balance_str), &control->volume), ++ pa_bvolume_snprint_balance(new_balance_str, sizeof(new_balance_str), &control->volume)); ++ } ++ ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_VOLUME_CHANGED], control); ++} ++ ++int pa_volume_control_set_volume(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance) { ++ pa_bvolume volume_local; ++ int r; ++ ++ pa_assert(control); ++ pa_assert(volume); ++ ++ volume_local = *volume; ++ ++ if (!control->set_volume) { ++ pa_log_info("Tried to set the volume of volume control %s, but the volume control doesn't support the operation.", ++ control->name); ++ return -PA_ERR_NOTSUPPORTED; ++ } ++ ++ if (set_balance ++ && !control->channel_map_is_writable ++ && !pa_channel_map_equal(&volume_local.channel_map, &control->volume.channel_map)) ++ pa_bvolume_remap(&volume_local, &control->volume.channel_map); ++ ++ if (pa_bvolume_equal(&volume_local, &control->volume, set_volume, set_balance)) ++ return 0; ++ ++ control->set_volume_in_progress = true; ++ r = control->set_volume(control, &volume_local, set_volume, set_balance); ++ control->set_volume_in_progress = false; ++ ++ if (r >= 0) ++ set_volume_internal(control, &volume_local, set_volume, set_balance); ++ ++ return r; ++} ++ ++void pa_volume_control_description_changed(pa_volume_control *control, const char *new_description) { ++ char *old_description; ++ ++ pa_assert(control); ++ pa_assert(new_description); ++ ++ old_description = control->description; ++ ++ if (pa_streq(new_description, old_description)) ++ return; ++ ++ control->description = pa_xstrdup(new_description); ++ pa_log_debug("The description of volume control %s changed from \"%s\" to \"%s\".", control->name, old_description, ++ new_description); ++ pa_xfree(old_description); ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_DESCRIPTION_CHANGED], control); ++} ++ ++void pa_volume_control_volume_changed(pa_volume_control *control, const pa_bvolume *new_volume, bool volume_changed, ++ bool balance_changed) { ++ pa_assert(control); ++ pa_assert(new_volume); ++ ++ if (!control->linked) ++ return; ++ ++ if (control->set_volume_in_progress) ++ return; ++ ++ set_volume_internal(control, new_volume, volume_changed, balance_changed); ++} ++ ++void pa_volume_control_add_device(pa_volume_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_put(control->devices, device, device) >= 0); ++} ++ ++void pa_volume_control_remove_device(pa_volume_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_remove(control->devices, device)); ++} ++ ++void pa_volume_control_add_default_for_device(pa_volume_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_put(control->default_for_devices, device, device) >= 0); ++} ++ ++void pa_volume_control_remove_default_for_device(pa_volume_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_remove(control->default_for_devices, device)); ++} ++ ++void pa_volume_control_add_stream(pa_volume_control *control, pas_stream *stream) { ++ pa_assert(control); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_put(control->streams, stream, stream) >= 0); ++} ++ ++void pa_volume_control_remove_stream(pa_volume_control *control, pas_stream *stream) { ++ pa_assert(control); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_remove(control->streams, stream)); ++} ++ ++void pa_volume_control_add_audio_group(pa_volume_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ pa_assert_se(pa_hashmap_put(control->audio_groups, group, group) >= 0); ++} ++ ++void pa_volume_control_remove_audio_group(pa_volume_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ pa_assert_se(pa_hashmap_remove(control->audio_groups, group)); ++} +diff --git a/src/modules/volume-api/volume-control.h b/src/modules/volume-api/volume-control.h +new file mode 100644 +index 0000000..aaba758 +--- /dev/null ++++ b/src/modules/volume-api/volume-control.h +@@ -0,0 +1,112 @@ ++#ifndef foovolumecontrolhfoo ++#define foovolumecontrolhfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++#include ++ ++typedef struct pa_volume_control pa_volume_control; ++ ++struct pa_volume_control { ++ pa_volume_api *volume_api; ++ uint32_t index; ++ const char *name; ++ char *description; ++ pa_proplist *proplist; ++ pa_bvolume volume; ++ bool convertible_to_dB; ++ bool channel_map_is_writable; ++ ++ /* If this volume control is the "own volume control" of an audio group, ++ * this is set to point to that group, otherwise this is NULL. */ ++ pa_audio_group *owner_audio_group; ++ ++ pa_hashmap *devices; /* pa_device -> pa_device (hashmap-as-a-set) */ ++ pa_hashmap *default_for_devices; /* pa_device -> pa_device (hashmap-as-a-set) */ ++ pa_hashmap *streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ ++ pa_hashmap *audio_groups; /* pa_audio_group -> pa_audio_group (hashmap-as-a-set) */ ++ ++ bool linked; ++ bool unlinked; ++ bool set_volume_in_progress; ++ ++ /* Called from pa_volume_control_set_volume(). The implementation is ++ * expected to return a negative error code on failure. May be NULL, if the ++ * volume control is read-only. */ ++ int (*set_volume)(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance); ++ ++ void *userdata; ++}; ++ ++pa_volume_control *pa_volume_control_new(pa_volume_api *api, const char *name, const char *description, bool convertible_to_dB, ++ bool channel_map_is_writable); ++ ++typedef void (*pa_volume_control_set_initial_volume_cb_t)(pa_volume_control *control); ++ ++/* initial_volume is the preferred initial volume of the volume control ++ * implementation. It may be NULL or partially invalid, if the implementation ++ * doesn't care about the initial state of the volume control, as long as these ++ * two rules are followed: ++ * ++ * 1) Read-only volume controls must always specify fully valid initial ++ * volume. ++ * 2) Volume controls with read-only channel map must always specify a valid ++ * channel map in initial_volume. ++ * ++ * The implementation's initial volume preference may be overridden by policy, ++ * if the volume control isn't read-only. When the final initial volume is ++ * known, the implementation is notified via set_initial_volume_cb (the volume ++ * can be read from control->volume). set_initial_volume_cb may be NULL, if the ++ * volume control is read-only. */ ++void pa_volume_control_put(pa_volume_control *control, const pa_bvolume *initial_volume, ++ pa_volume_control_set_initial_volume_cb_t set_initial_volume_cb); ++ ++void pa_volume_control_unlink(pa_volume_control *control); ++void pa_volume_control_free(pa_volume_control *control); ++ ++/* Called by audio-group.c only. */ ++void pa_volume_control_set_owner_audio_group(pa_volume_control *control, pa_audio_group *group); ++ ++/* Called by clients and policy modules. */ ++int pa_volume_control_set_volume(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance); ++ ++/* Called by the volume control implementation. */ ++void pa_volume_control_description_changed(pa_volume_control *control, const char *new_description); ++void pa_volume_control_volume_changed(pa_volume_control *control, const pa_bvolume *new_volume, bool volume_changed, ++ bool balance_changed); ++ ++/* Called from device.c only. */ ++void pa_volume_control_add_device(pa_volume_control *control, pa_device *device); ++void pa_volume_control_remove_device(pa_volume_control *control, pa_device *device); ++void pa_volume_control_add_default_for_device(pa_volume_control *control, pa_device *device); ++void pa_volume_control_remove_default_for_device(pa_volume_control *control, pa_device *device); ++ ++/* Called from sstream.c only. */ ++void pa_volume_control_add_stream(pa_volume_control *control, pas_stream *stream); ++void pa_volume_control_remove_stream(pa_volume_control *control, pas_stream *stream); ++ ++/* Called from audio-group.c only. */ ++void pa_volume_control_add_audio_group(pa_volume_control *control, pa_audio_group *group); ++void pa_volume_control_remove_audio_group(pa_volume_control *control, pa_audio_group *group); ++ ++#endif +diff --git a/src/pulse/ext-volume-api.c b/src/pulse/ext-volume-api.c +new file mode 100644 +index 0000000..8e93bce +--- /dev/null ++++ b/src/pulse/ext-volume-api.c +@@ -0,0 +1,275 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "ext-volume-api.h" ++ ++#include ++#include ++#include ++ ++#include ++ ++int pa_ext_volume_api_balance_valid(double balance) { ++ return balance >= 0.0 && balance <= 1.0; ++} ++ ++int pa_ext_volume_api_bvolume_valid(const pa_ext_volume_api_bvolume *volume, int check_volume, int check_balance) { ++ unsigned channel; ++ ++ pa_assert(volume); ++ ++ if (check_volume && !PA_VOLUME_IS_VALID(volume->volume)) ++ return 0; ++ ++ if (!check_balance) ++ return 1; ++ ++ if (!pa_channel_map_valid(&volume->channel_map)) ++ return 0; ++ ++ for (channel = 0; channel < volume->channel_map.channels; channel++) { ++ if (!pa_ext_volume_api_balance_valid(volume->balance[channel])) ++ return 0; ++ } ++ ++ return 1; ++} ++ ++void pa_ext_volume_api_bvolume_init_invalid(pa_ext_volume_api_bvolume *volume) { ++ unsigned i; ++ ++ pa_assert(volume); ++ ++ volume->volume = PA_VOLUME_INVALID; ++ ++ for (i = 0; i < PA_CHANNELS_MAX; i++) ++ volume->balance[i] = -1.0; ++ ++ pa_channel_map_init(&volume->channel_map); ++} ++ ++void pa_ext_volume_api_bvolume_init_mono(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume) { ++ pa_assert(bvolume); ++ pa_assert(PA_VOLUME_IS_VALID(volume)); ++ ++ bvolume->volume = volume; ++ bvolume->balance[0] = 1.0; ++ pa_channel_map_init_mono(&bvolume->channel_map); ++} ++ ++int pa_ext_volume_api_bvolume_equal(const pa_ext_volume_api_bvolume *a, const pa_ext_volume_api_bvolume *b, ++ int check_volume, int check_balance) { ++ unsigned i; ++ ++ pa_assert(a); ++ pa_assert(b); ++ ++ if (check_volume && a->volume != b->volume) ++ return 0; ++ ++ if (!check_balance) ++ return 1; ++ ++ if (!pa_channel_map_equal(&a->channel_map, &b->channel_map)) ++ return 0; ++ ++ for (i = 0; i < a->channel_map.channels; i++) { ++ if (fabs(a->balance[i] - b->balance[i]) > 0.00001) ++ return 0; ++ } ++ ++ return 1; ++} ++ ++void pa_ext_volume_api_bvolume_from_cvolume(pa_ext_volume_api_bvolume *bvolume, const pa_cvolume *cvolume, ++ const pa_channel_map *map) { ++ unsigned i; ++ ++ pa_assert(bvolume); ++ pa_assert(cvolume); ++ pa_assert(map); ++ pa_assert(cvolume->channels == map->channels); ++ ++ bvolume->volume = pa_cvolume_max(cvolume); ++ bvolume->channel_map = *map; ++ ++ for (i = 0; i < map->channels; i++) { ++ if (bvolume->volume != PA_VOLUME_MUTED) ++ bvolume->balance[i] = ((double) cvolume->values[i]) / ((double) bvolume->volume); ++ else ++ bvolume->balance[i] = 1.0; ++ } ++} ++ ++void pa_ext_volume_api_bvolume_to_cvolume(const pa_ext_volume_api_bvolume *bvolume, pa_cvolume *cvolume) { ++ unsigned i; ++ ++ pa_assert(bvolume); ++ pa_assert(cvolume); ++ pa_assert(pa_ext_volume_api_bvolume_valid(bvolume, true, true)); ++ ++ cvolume->channels = bvolume->channel_map.channels; ++ ++ for (i = 0; i < bvolume->channel_map.channels; i++) ++ cvolume->values[i] = bvolume->volume * bvolume->balance[i]; ++} ++ ++void pa_ext_volume_api_bvolume_copy_balance(pa_ext_volume_api_bvolume *to, ++ const pa_ext_volume_api_bvolume *from) { ++ pa_assert(to); ++ pa_assert(from); ++ ++ memcpy(to->balance, from->balance, sizeof(from->balance)); ++ to->channel_map = from->channel_map; ++} ++ ++void pa_ext_volume_api_bvolume_reset_balance(pa_ext_volume_api_bvolume *volume, const pa_channel_map *map) { ++ unsigned i; ++ ++ pa_assert(volume); ++ pa_assert(map); ++ pa_assert(pa_channel_map_valid(map)); ++ ++ for (i = 0; i < map->channels; i++) ++ volume->balance[i] = 1.0; ++ ++ volume->channel_map = *map; ++} ++ ++void pa_ext_volume_api_bvolume_remap(pa_ext_volume_api_bvolume *volume, const pa_channel_map *to) { ++ unsigned i; ++ pa_cvolume cvolume; ++ ++ pa_assert(volume); ++ pa_assert(to); ++ pa_assert(pa_ext_volume_api_bvolume_valid(volume, false, true)); ++ pa_assert(pa_channel_map_valid(to)); ++ ++ cvolume.channels = volume->channel_map.channels; ++ ++ for (i = 0; i < cvolume.channels; i++) ++ cvolume.values[i] = volume->balance[i] * (double) PA_VOLUME_NORM; ++ ++ pa_cvolume_remap(&cvolume, &volume->channel_map, to); ++ ++ for (i = 0; i < to->channels; i++) ++ volume->balance[i] = (double) cvolume.values[i] / (double) PA_VOLUME_NORM; ++ ++ volume->channel_map = *to; ++} ++ ++double pa_ext_volume_api_bvolume_get_left_right_balance(const pa_ext_volume_api_bvolume *volume) { ++ pa_ext_volume_api_bvolume bvolume; ++ pa_cvolume cvolume; ++ double ret; ++ ++ pa_assert(volume); ++ ++ bvolume.volume = PA_VOLUME_NORM; ++ pa_ext_volume_api_bvolume_copy_balance(&bvolume, volume); ++ pa_ext_volume_api_bvolume_to_cvolume(&bvolume, &cvolume); ++ ret = pa_cvolume_get_balance(&cvolume, &volume->channel_map); ++ ++ return ret; ++} ++ ++void pa_ext_volume_api_bvolume_set_left_right_balance(pa_ext_volume_api_bvolume *volume, double balance) { ++ pa_cvolume cvolume; ++ pa_volume_t old_volume; ++ ++ pa_assert(volume); ++ ++ if (!pa_channel_map_can_balance(&volume->channel_map)) ++ return; ++ ++ pa_cvolume_reset(&cvolume, volume->channel_map.channels); ++ pa_cvolume_set_balance(&cvolume, &volume->channel_map, balance); ++ old_volume = volume->volume; ++ pa_ext_volume_api_bvolume_from_cvolume(volume, &cvolume, &volume->channel_map); ++ volume->volume = old_volume; ++} ++ ++double pa_ext_volume_api_bvolume_get_rear_front_balance(const pa_ext_volume_api_bvolume *volume) { ++ pa_ext_volume_api_bvolume bvolume; ++ pa_cvolume cvolume; ++ double ret; ++ ++ pa_assert(volume); ++ ++ bvolume.volume = PA_VOLUME_NORM; ++ pa_ext_volume_api_bvolume_copy_balance(&bvolume, volume); ++ pa_ext_volume_api_bvolume_to_cvolume(&bvolume, &cvolume); ++ ret = pa_cvolume_get_fade(&cvolume, &volume->channel_map); ++ ++ return ret; ++} ++ ++void pa_ext_volume_api_bvolume_set_rear_front_balance(pa_ext_volume_api_bvolume *volume, double balance) { ++ pa_cvolume cvolume; ++ pa_volume_t old_volume; ++ ++ pa_assert(volume); ++ ++ if (!pa_channel_map_can_fade(&volume->channel_map)) ++ return; ++ ++ pa_cvolume_reset(&cvolume, volume->channel_map.channels); ++ pa_cvolume_set_fade(&cvolume, &volume->channel_map, balance); ++ old_volume = volume->volume; ++ pa_ext_volume_api_bvolume_from_cvolume(volume, &cvolume, &volume->channel_map); ++ volume->volume = old_volume; ++} ++ ++char *pa_ext_volume_api_bvolume_snprint_balance(char *buf, size_t buf_len, ++ const pa_ext_volume_api_bvolume *volume) { ++ char *e; ++ unsigned channel; ++ bool first = true; ++ ++ pa_assert(buf); ++ pa_assert(buf_len > 0); ++ pa_assert(volume); ++ ++ pa_init_i18n(); ++ ++ if (!pa_ext_volume_api_bvolume_valid(volume, true, true)) { ++ pa_snprintf(buf, buf_len, _("(invalid)")); ++ return buf; ++ } ++ ++ *(e = buf) = 0; ++ ++ for (channel = 0; channel < volume->channel_map.channels && buf_len > 1; channel++) { ++ buf_len -= pa_snprintf(e, buf_len, "%s%s: %u%%", ++ first ? "" : ", ", ++ pa_channel_position_to_string(volume->channel_map.map[channel]), ++ (unsigned) (volume->balance[channel] * 100 + 0.5)); ++ ++ e = strchr(e, 0); ++ first = false; ++ } ++ ++ return buf; ++} +diff --git a/src/pulse/ext-volume-api.h b/src/pulse/ext-volume-api.h +new file mode 100644 +index 0000000..36b7748 +--- /dev/null ++++ b/src/pulse/ext-volume-api.h +@@ -0,0 +1,68 @@ ++#ifndef fooextvolumeapihfoo ++#define fooextvolumeapihfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++#include ++#include ++ ++/* This API is temporary, and has no stability guarantees whatsoever. Think ++ * twice before making anything that relies on this API. This is undocumented ++ * for a reason. */ ++ ++PA_C_DECL_BEGIN ++ ++typedef struct pa_ext_volume_api_bvolume pa_ext_volume_api_bvolume; ++ ++struct pa_ext_volume_api_bvolume { ++ pa_volume_t volume; ++ double balance[PA_CHANNELS_MAX]; ++ pa_channel_map channel_map; ++}; ++ ++int pa_ext_volume_api_balance_valid(double balance) PA_GCC_CONST; ++int pa_ext_volume_api_bvolume_valid(const pa_ext_volume_api_bvolume *volume, int check_volume, int check_balance) ++ PA_GCC_PURE; ++void pa_ext_volume_api_bvolume_init_invalid(pa_ext_volume_api_bvolume *volume); ++void pa_ext_volume_api_bvolume_init_mono(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume); ++int pa_ext_volume_api_bvolume_equal(const pa_ext_volume_api_bvolume *a, const pa_ext_volume_api_bvolume *b, ++ int check_volume, int check_balance) PA_GCC_PURE; ++void pa_ext_volume_api_bvolume_from_cvolume(pa_ext_volume_api_bvolume *bvolume, const pa_cvolume *cvolume, ++ const pa_channel_map *map); ++void pa_ext_volume_api_bvolume_to_cvolume(const pa_ext_volume_api_bvolume *bvolume, pa_cvolume *cvolume); ++void pa_ext_volume_api_bvolume_copy_balance(pa_ext_volume_api_bvolume *to, ++ const pa_ext_volume_api_bvolume *from); ++void pa_ext_volume_api_bvolume_reset_balance(pa_ext_volume_api_bvolume *volume, const pa_channel_map *map); ++void pa_ext_volume_api_bvolume_remap(pa_ext_volume_api_bvolume *volume, const pa_channel_map *to); ++double pa_ext_volume_api_bvolume_get_left_right_balance(const pa_ext_volume_api_bvolume *volume) PA_GCC_PURE; ++void pa_ext_volume_api_bvolume_set_left_right_balance(pa_ext_volume_api_bvolume *volume, double balance); ++double pa_ext_volume_api_bvolume_get_rear_front_balance(const pa_ext_volume_api_bvolume *volume) PA_GCC_PURE; ++void pa_ext_volume_api_bvolume_set_rear_front_balance(pa_ext_volume_api_bvolume *volume, double balance); ++ ++#define PA_EXT_VOLUME_API_BVOLUME_SNPRINT_BALANCE_MAX 500 ++char *pa_ext_volume_api_bvolume_snprint_balance(char *buf, size_t buf_size, ++ const pa_ext_volume_api_bvolume *volume); ++ ++PA_C_DECL_END ++ ++#endif +-- +2.1.4 + +--- a/po/POTFILES.in 2016-04-13 17:40:00.818008672 +0200 ++++ b/po/POTFILES.in 2016-04-13 17:40:30.885008622 +0200 +@@ -198,3 +198,5 @@ + src/utils/padsp.c + src/utils/pasuspender.c + src/utils/pax11publish.c ++src/modules/volume-api/device-creator.c ++src/pulse/ext-volume-api.c diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0031-Add-module-main-volume-policy.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0031-Add-module-main-volume-policy.patch new file mode 100644 index 000000000..216ed21e9 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0031-Add-module-main-volume-policy.patch @@ -0,0 +1,1418 @@ +From cfb39f18569679f59c9b6283c47e8d90ddd9763d Mon Sep 17 00:00:00 2001 +From: Tanu Kaskinen +Date: Wed, 21 May 2014 14:13:41 +0300 +Subject: [PATCH] Add module-main-volume-policy + +Change-Id: I787141b43cafb652aa752c64ae28b6b7aa052d8e +Signed-off-by: Jaska Uimonen +--- + Makefile.am | 3 + + src/Makefile.am | 15 + + src/daemon/default.pa.in | 4 + + .../main-volume-policy/main-volume-context.c | 325 ++++++++++++ + .../main-volume-policy/main-volume-context.h | 75 +++ + .../main-volume-policy/main-volume-policy.c | 213 ++++++++ + .../main-volume-policy.conf.example | 20 + + .../main-volume-policy/main-volume-policy.h | 72 +++ + .../main-volume-policy/module-main-volume-policy.c | 556 +++++++++++++++++++++ + 9 files changed, 1283 insertions(+) + create mode 100644 src/modules/main-volume-policy/main-volume-context.c + create mode 100644 src/modules/main-volume-policy/main-volume-context.h + create mode 100644 src/modules/main-volume-policy/main-volume-policy.c + create mode 100644 src/modules/main-volume-policy/main-volume-policy.conf.example + create mode 100644 src/modules/main-volume-policy/main-volume-policy.h + create mode 100644 src/modules/main-volume-policy/module-main-volume-policy.c + +diff --git a/Makefile.am b/Makefile.am +index cf4a648..646b7fc 100644 +--- a/Makefile.am 2016-04-13 15:14:28.942023245 +0200 ++++ b/Makefile.am 2016-04-13 15:16:32.691023039 +0200 +@@ -60,6 +60,9 @@ + moduledevvolumeapi_DATA = src/modules/volume-api/*.h + moduledevvolumeapidir = $(includedir)/pulsemodule/modules/volume-api + ++moduledevmainvolumepolicy_DATA = $(top_srcdir)/src/modules/main-volume-policy/*.h ++moduledevmainvolumepolicydir = $(includedir)/pulsemodule/modules/main-volume-policy ++ + if HAVE_GLIB20 + pkgconfig_DATA += \ + libpulse-mainloop-glib.pc + libpulse-mainloop-glib.pc +diff --git a/src/Makefile.am b/src/Makefile.am +index a6bb319..8fa60ec 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -1050,7 +1050,8 @@ + libprotocol-simple.la \ + libprotocol-http.la \ + libprotocol-native.la \ +- libvolume-api.la ++ libvolume-api.la \ ++ libmain-volume-policy.la + + if HAVE_WEBRTC + modlibexec_LTLIBRARIES += libwebrtc-util.la +@@ -1051,6 +1052,12 @@ libcli_la_SOURCES = pulsecore/cli.c pulsecore/cli.h + libcli_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version + libcli_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la + ++libmain_volume_policy_la_SOURCES = \ ++ modules/main-volume-policy/main-volume-context.c modules/main-volume-policy/main-volume-context.h \ ++ modules/main-volume-policy/main-volume-policy.c modules/main-volume-policy/main-volume-policy.h ++libmain_volume_policy_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version ++libmain_volume_policy_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la libvolume-api.la ++ + libprotocol_cli_la_SOURCES = pulsecore/protocol-cli.c pulsecore/protocol-cli.h + libprotocol_cli_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version + libprotocol_cli_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la libcli.la +@@ -1136,6 +1136,7 @@ endif + modlibexec_LTLIBRARIES += \ + module-cli.la \ + module-cli-protocol-tcp.la \ ++ module-main-volume-policy.la \ + module-simple-protocol-tcp.la \ + module-null-sink.la \ + module-null-source.la \ +@@ -1426,6 +1434,7 @@ SYMDEF_FILES = \ + module-cli-symdef.h \ + module-cli-protocol-tcp-symdef.h \ + module-cli-protocol-unix-symdef.h \ ++ module-main-volume-policy-symdef.h \ + module-pipe-sink-symdef.h \ + module-pipe-source-symdef.h \ + module-simple-protocol-tcp-symdef.h \ +@@ -1575,6 +1584,12 @@ module_cli_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_CLI $(AM_ + module_cli_protocol_unix_la_LDFLAGS = $(MODULE_LDFLAGS) + module_cli_protocol_unix_la_LIBADD = $(MODULE_LIBADD) libprotocol-cli.la + ++# Main volume and mute policy ++ ++module_main_volume_policy_la_SOURCES = modules/main-volume-policy/module-main-volume-policy.c ++module_main_volume_policy_la_LDFLAGS = $(MODULE_LDFLAGS) ++module_main_volume_policy_la_LIBADD = $(MODULE_LIBADD) libmain-volume-policy.la libvolume-api.la ++ + # HTTP protocol + + module_http_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c +diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in +index 7cf52a4..f70804c 100755 +--- a/src/daemon/default.pa.in ++++ b/src/daemon/default.pa.in +@@ -188,6 +188,10 @@ + #.endif + ])dnl + ++.ifexists module-main-volume-policy ++load-module module-main-volume-policy ++.endif ++ + ### Make some devices default + #set-default-sink output + #set-default-source input +diff --git a/src/modules/main-volume-policy/main-volume-context.c b/src/modules/main-volume-policy/main-volume-context.c +new file mode 100644 +index 0000000..7ac35c6 +--- /dev/null ++++ b/src/modules/main-volume-policy/main-volume-context.c +@@ -0,0 +1,325 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "main-volume-context.h" ++ ++#include ++#include ++ ++int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, const char *description, ++ pa_main_volume_context **context) { ++ pa_main_volume_context *context_local; ++ int r; ++ ++ pa_assert(policy); ++ pa_assert(name); ++ pa_assert(description); ++ pa_assert(context); ++ ++ context_local = pa_xnew0(struct pa_main_volume_context, 1); ++ context_local->main_volume_policy = policy; ++ context_local->index = pa_main_volume_policy_allocate_main_volume_context_index(policy); ++ ++ r = pa_main_volume_policy_register_name(policy, name, true, &context_local->name); ++ if (r < 0) ++ goto fail; ++ ++ context_local->description = pa_xstrdup(description); ++ ++ *context = context_local; ++ ++ return 0; ++ ++fail: ++ pa_main_volume_context_free(context_local); ++ ++ return r; ++} ++ ++void pa_main_volume_context_put(pa_main_volume_context *context) { ++ pa_assert(context); ++ ++ pa_main_volume_policy_add_main_volume_context(context->main_volume_policy, context); ++ ++ context->linked = true; ++ ++ pa_log_debug("Created main volume context #%u.", context->index); ++ pa_log_debug(" Name: %s", context->name); ++ pa_log_debug(" Description: %s", context->description); ++ pa_log_debug(" Main output volume control: %s", ++ context->main_output_volume_control ? context->main_output_volume_control->name : "(unset)"); ++ pa_log_debug(" Main input volume control: %s", ++ context->main_input_volume_control ? context->main_input_volume_control->name : "(unset)"); ++ pa_log_debug(" Main output mute control: %s", ++ context->main_output_mute_control ? context->main_output_mute_control->name : "(unset)"); ++ pa_log_debug(" Main input mute control: %s", ++ context->main_input_mute_control ? context->main_input_mute_control->name : "(unset)"); ++ ++ pa_hook_fire(&context->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_PUT], context); ++} ++ ++void pa_main_volume_context_unlink(pa_main_volume_context *context) { ++ pa_assert(context); ++ ++ if (context->unlinked) { ++ pa_log_debug("Unlinking main volume context %s (already unlinked, this is a no-op).", context->name); ++ return; ++ } ++ ++ context->unlinked = true; ++ ++ pa_log_debug("Unlinking main volume context %s.", context->name); ++ ++ if (context->linked) ++ pa_hook_fire(&context->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK], context); ++ ++ if (context->main_input_mute_control_binding) { ++ pa_binding_free(context->main_input_mute_control_binding); ++ context->main_input_mute_control_binding = NULL; ++ } ++ ++ if (context->main_output_mute_control_binding) { ++ pa_binding_free(context->main_output_mute_control_binding); ++ context->main_output_mute_control_binding = NULL; ++ } ++ ++ if (context->main_input_volume_control_binding) { ++ pa_binding_free(context->main_input_volume_control_binding); ++ context->main_input_volume_control_binding = NULL; ++ } ++ ++ if (context->main_output_volume_control_binding) { ++ pa_binding_free(context->main_output_volume_control_binding); ++ context->main_output_volume_control_binding = NULL; ++ } ++ ++ context->main_input_mute_control = NULL; ++ context->main_output_mute_control = NULL; ++ context->main_input_volume_control = NULL; ++ context->main_output_volume_control = NULL; ++ ++ pa_main_volume_policy_remove_main_volume_context(context->main_volume_policy, context); ++} ++ ++void pa_main_volume_context_free(pa_main_volume_context *context) { ++ pa_assert(context); ++ ++ if (!context->unlinked) ++ pa_main_volume_context_unlink(context); ++ ++ pa_xfree(context->description); ++ ++ if (context->name) ++ pa_main_volume_policy_unregister_name(context->main_volume_policy, context->name); ++ ++ pa_xfree(context); ++} ++ ++const char *pa_main_volume_context_get_name(pa_main_volume_context *context) { ++ pa_assert(context); ++ ++ return context->name; ++} ++ ++static void set_main_output_volume_control_internal(pa_main_volume_context *context, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(context); ++ ++ old_control = context->main_output_volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ context->main_output_volume_control = control; ++ ++ if (!context->linked || context->unlinked) ++ return; ++ ++ pa_log_debug("The main output volume control of main volume context %s changed from %s to %s.", context->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&context->main_volume_policy->hooks ++ [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED], ++ context); ++} ++ ++void pa_main_volume_context_bind_main_output_volume_control(pa_main_volume_context *context, ++ pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = context, ++ .set_value = (pa_binding_set_value_cb_t) set_main_output_volume_control_internal, ++ }; ++ ++ pa_assert(context); ++ pa_assert(target_info); ++ ++ if (context->main_output_volume_control_binding) ++ pa_binding_free(context->main_output_volume_control_binding); ++ ++ context->main_output_volume_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info, ++ target_info); ++} ++ ++static void set_main_input_volume_control_internal(pa_main_volume_context *context, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(context); ++ ++ old_control = context->main_input_volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ context->main_input_volume_control = control; ++ ++ if (!context->linked || context->unlinked) ++ return; ++ ++ pa_log_debug("The main input volume control of main volume context %s changed from %s to %s.", context->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&context->main_volume_policy->hooks ++ [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_VOLUME_CONTROL_CHANGED], ++ context); ++} ++ ++void pa_main_volume_context_bind_main_input_volume_control(pa_main_volume_context *context, ++ pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = context, ++ .set_value = (pa_binding_set_value_cb_t) set_main_input_volume_control_internal, ++ }; ++ ++ pa_assert(context); ++ pa_assert(target_info); ++ ++ if (context->main_input_volume_control_binding) ++ pa_binding_free(context->main_input_volume_control_binding); ++ ++ context->main_input_volume_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info, ++ target_info); ++} ++ ++static void set_main_output_mute_control_internal(pa_main_volume_context *context, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(context); ++ ++ old_control = context->main_output_mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ context->main_output_mute_control = control; ++ ++ if (!context->linked || context->unlinked) ++ return; ++ ++ pa_log_debug("The main output mute control of main volume context %s changed from %s to %s.", context->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&context->main_volume_policy->hooks ++ [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_MUTE_CONTROL_CHANGED], ++ context); ++} ++ ++void pa_main_volume_context_bind_main_output_mute_control(pa_main_volume_context *context, ++ pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = context, ++ .set_value = (pa_binding_set_value_cb_t) set_main_output_mute_control_internal, ++ }; ++ ++ pa_assert(context); ++ pa_assert(target_info); ++ ++ if (context->main_output_mute_control_binding) ++ pa_binding_free(context->main_output_mute_control_binding); ++ ++ context->main_output_mute_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info, ++ target_info); ++} ++ ++static void set_main_input_mute_control_internal(pa_main_volume_context *context, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(context); ++ ++ old_control = context->main_input_mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ context->main_input_mute_control = control; ++ ++ if (!context->linked || context->unlinked) ++ return; ++ ++ pa_log_debug("The main input mute control of main volume context %s changed from %s to %s.", context->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&context->main_volume_policy->hooks ++ [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_MUTE_CONTROL_CHANGED], ++ context); ++} ++ ++void pa_main_volume_context_bind_main_input_mute_control(pa_main_volume_context *context, ++ pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = context, ++ .set_value = (pa_binding_set_value_cb_t) set_main_input_mute_control_internal, ++ }; ++ ++ pa_assert(context); ++ pa_assert(target_info); ++ ++ if (context->main_input_mute_control_binding) ++ pa_binding_free(context->main_input_mute_control_binding); ++ ++ context->main_input_mute_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info, ++ target_info); ++} ++ ++pa_binding_target_type *pa_main_volume_context_create_binding_target_type(pa_main_volume_policy *policy) { ++ pa_binding_target_type *type; ++ ++ pa_assert(policy); ++ ++ type = pa_binding_target_type_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, policy->main_volume_contexts, ++ &policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_PUT], ++ &policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK], ++ (pa_binding_target_type_get_name_cb_t) pa_main_volume_context_get_name); ++ pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL, ++ PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_output_volume_control)); ++ pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL, ++ PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_input_volume_control)); ++ pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL, ++ PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_output_mute_control)); ++ pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL, ++ PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_input_mute_control)); ++ ++ return type; ++} +diff --git a/src/modules/main-volume-policy/main-volume-context.h b/src/modules/main-volume-policy/main-volume-context.h +new file mode 100644 +index 0000000..4a0a6f7 +--- /dev/null ++++ b/src/modules/main-volume-policy/main-volume-context.h +@@ -0,0 +1,75 @@ ++#ifndef foomainvolumecontexthfoo ++#define foomainvolumecontexthfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++ ++#include ++ ++typedef struct pa_main_volume_context pa_main_volume_context; ++ ++#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE "MainVolumeContext" ++#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL "main_output_volume_control" ++#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL "main_input_volume_control" ++#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL "main_output_mute_control" ++#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL "main_input_mute_control" ++ ++struct pa_main_volume_context { ++ pa_main_volume_policy *main_volume_policy; ++ uint32_t index; ++ const char *name; ++ char *description; ++ pa_volume_control *main_output_volume_control; ++ pa_volume_control *main_input_volume_control; ++ pa_mute_control *main_output_mute_control; ++ pa_mute_control *main_input_mute_control; ++ ++ pa_binding *main_output_volume_control_binding; ++ pa_binding *main_input_volume_control_binding; ++ pa_binding *main_output_mute_control_binding; ++ pa_binding *main_input_mute_control_binding; ++ ++ bool linked; ++ bool unlinked; ++}; ++ ++int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, const char *description, ++ pa_main_volume_context **context); ++void pa_main_volume_context_put(pa_main_volume_context *context); ++void pa_main_volume_context_unlink(pa_main_volume_context *context); ++void pa_main_volume_context_free(pa_main_volume_context *context); ++ ++const char *pa_main_volume_context_get_name(pa_main_volume_context *context); ++ ++void pa_main_volume_context_bind_main_output_volume_control(pa_main_volume_context *context, ++ pa_binding_target_info *target_info); ++void pa_main_volume_context_bind_main_input_volume_control(pa_main_volume_context *context, ++ pa_binding_target_info *target_info); ++void pa_main_volume_context_bind_main_output_mute_control(pa_main_volume_context *context, ++ pa_binding_target_info *target_info); ++void pa_main_volume_context_bind_main_input_mute_control(pa_main_volume_context *context, pa_binding_target_info *target_info); ++ ++/* Called from main-volume-policy.c only. */ ++pa_binding_target_type *pa_main_volume_context_create_binding_target_type(pa_main_volume_policy *policy); ++ ++#endif +diff --git a/src/modules/main-volume-policy/main-volume-policy.c b/src/modules/main-volume-policy/main-volume-policy.c +new file mode 100644 +index 0000000..b0b4ede +--- /dev/null ++++ b/src/modules/main-volume-policy/main-volume-policy.c +@@ -0,0 +1,213 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "main-volume-policy.h" ++ ++#include ++ ++#include ++#include ++ ++static pa_main_volume_policy *main_volume_policy_new(pa_core *core); ++static void main_volume_policy_free(pa_main_volume_policy *policy); ++ ++pa_main_volume_policy *pa_main_volume_policy_get(pa_core *core) { ++ pa_main_volume_policy *policy; ++ ++ pa_assert(core); ++ ++ policy = pa_shared_get(core, "main-volume-policy"); ++ ++ if (policy) ++ pa_main_volume_policy_ref(policy); ++ else { ++ policy = main_volume_policy_new(core); ++ pa_assert_se(pa_shared_set(core, "main-volume-policy", policy) >= 0); ++ } ++ ++ return policy; ++} ++ ++pa_main_volume_policy *pa_main_volume_policy_ref(pa_main_volume_policy *policy) { ++ pa_assert(policy); ++ ++ policy->refcnt++; ++ ++ return policy; ++} ++ ++void pa_main_volume_policy_unref(pa_main_volume_policy *policy) { ++ pa_assert(policy); ++ pa_assert(policy->refcnt > 0); ++ ++ policy->refcnt--; ++ ++ if (policy->refcnt == 0) { ++ pa_assert_se(pa_shared_remove(policy->core, "main-volume-policy") >= 0); ++ main_volume_policy_free(policy); ++ } ++} ++ ++static pa_main_volume_policy *main_volume_policy_new(pa_core *core) { ++ pa_main_volume_policy *policy; ++ unsigned i; ++ ++ pa_assert(core); ++ ++ policy = pa_xnew0(pa_main_volume_policy, 1); ++ policy->core = core; ++ policy->refcnt = 1; ++ policy->volume_api = pa_volume_api_get(core); ++ policy->names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree); ++ policy->main_volume_contexts = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ ++ for (i = 0; i < PA_MAIN_VOLUME_POLICY_HOOK_MAX; i++) ++ pa_hook_init(&policy->hooks[i], policy); ++ ++ policy->main_volume_context_binding_target_type = pa_main_volume_context_create_binding_target_type(policy); ++ pa_volume_api_add_binding_target_type(policy->volume_api, policy->main_volume_context_binding_target_type); ++ ++ pa_log_debug("Created a pa_main_volume_policy object."); ++ ++ return policy; ++} ++ ++static void main_volume_policy_free(pa_main_volume_policy *policy) { ++ unsigned i; ++ ++ pa_assert(policy); ++ pa_assert(policy->refcnt == 0); ++ ++ pa_log_debug("Freeing the pa_main_volume_policy object."); ++ ++ if (policy->main_volume_context_binding_target_type) { ++ pa_volume_api_remove_binding_target_type(policy->volume_api, policy->main_volume_context_binding_target_type); ++ pa_binding_target_type_free(policy->main_volume_context_binding_target_type); ++ } ++ ++ for (i = 0; i < PA_MAIN_VOLUME_POLICY_HOOK_MAX; i++) ++ pa_hook_done(&policy->hooks[i]); ++ ++ if (policy->main_volume_contexts) { ++ pa_assert(pa_hashmap_isempty(policy->main_volume_contexts)); ++ pa_hashmap_free(policy->main_volume_contexts); ++ } ++ ++ if (policy->names) { ++ pa_assert(pa_hashmap_isempty(policy->names)); ++ pa_hashmap_free(policy->names); ++ } ++ ++ if (policy->volume_api) ++ pa_volume_api_unref(policy->volume_api); ++ ++ pa_xfree(policy); ++} ++ ++int pa_main_volume_policy_register_name(pa_main_volume_policy *policy, const char *requested_name, ++ bool fail_if_already_registered, const char **registered_name) { ++ char *n; ++ ++ pa_assert(policy); ++ pa_assert(requested_name); ++ pa_assert(registered_name); ++ ++ n = pa_xstrdup(requested_name); ++ ++ if (pa_hashmap_put(policy->names, n, n) < 0) { ++ unsigned i = 1; ++ ++ pa_xfree(n); ++ ++ if (fail_if_already_registered) { ++ pa_log("Name %s already registered.", requested_name); ++ return -PA_ERR_EXIST; ++ } ++ ++ do { ++ i++; ++ n = pa_sprintf_malloc("%s.%u", requested_name, i); ++ } while (pa_hashmap_put(policy->names, n, n) < 0); ++ } ++ ++ *registered_name = n; ++ ++ return 0; ++} ++ ++void pa_main_volume_policy_unregister_name(pa_main_volume_policy *policy, const char *name) { ++ pa_assert(policy); ++ pa_assert(name); ++ ++ pa_assert_se(pa_hashmap_remove_and_free(policy->names, name) >= 0); ++} ++ ++uint32_t pa_main_volume_policy_allocate_main_volume_context_index(pa_main_volume_policy *policy) { ++ uint32_t idx; ++ ++ pa_assert(policy); ++ ++ idx = policy->next_main_volume_context_index++; ++ ++ return idx; ++} ++ ++void pa_main_volume_policy_add_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context) { ++ pa_assert(policy); ++ pa_assert(context); ++ ++ pa_assert_se(pa_hashmap_put(policy->main_volume_contexts, (void *) context->name, context) >= 0); ++} ++ ++int pa_main_volume_policy_remove_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context) { ++ pa_assert(policy); ++ pa_assert(context); ++ ++ if (!pa_hashmap_remove(policy->main_volume_contexts, context->name)) ++ return -1; ++ ++ if (context == policy->active_main_volume_context) ++ pa_main_volume_policy_set_active_main_volume_context(policy, NULL); ++ ++ return 0; ++} ++ ++void pa_main_volume_policy_set_active_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context) { ++ pa_main_volume_context *old_context; ++ ++ pa_assert(policy); ++ ++ old_context = policy->active_main_volume_context; ++ ++ if (context == old_context) ++ return; ++ ++ policy->active_main_volume_context = context; ++ ++ pa_log_debug("The active main volume context changed from %s to %s.", old_context ? old_context->name : "(unset)", ++ context ? context->name : "(unset)"); ++ ++ pa_hook_fire(&policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_ACTIVE_MAIN_VOLUME_CONTEXT_CHANGED], NULL); ++} +diff --git a/src/modules/main-volume-policy/main-volume-policy.conf.example b/src/modules/main-volume-policy/main-volume-policy.conf.example +new file mode 100644 +index 0000000..a4a35d3 +--- /dev/null ++++ b/src/modules/main-volume-policy/main-volume-policy.conf.example +@@ -0,0 +1,20 @@ ++[General] ++output-volume-model = by-active-main-volume-context ++input-volume-model = by-active-main-volume-context ++output-mute-model = none ++input-mute-model = none ++main-volume-contexts = x-example-call-main-volume-context x-example-default-main-volume-context ++ ++[MainVolumeContext x-example-call-main-volume-context] ++description = Call main volume context ++main-output-volume-control = bind:AudioGroup:x-example-call-downlink-audio-group ++main-input-volume-control = bind:AudioGroup:x-example-call-uplink-audio-group ++main-output-mute-control = none ++main-input-mute-control = none ++ ++[MainVolumeContext x-example-default-main-volume-context] ++description = Default main volume context ++main-output-volume-control = bind:AudioGroup:x-example-default-output-audio-group ++main-input-volume-control = bind:AudioGroup:x-example-default-input-audio-group ++main-output-mute-control = none ++main-input-mute-control = none +diff --git a/src/modules/main-volume-policy/main-volume-policy.h b/src/modules/main-volume-policy/main-volume-policy.h +new file mode 100644 +index 0000000..5cd669e +--- /dev/null ++++ b/src/modules/main-volume-policy/main-volume-policy.h +@@ -0,0 +1,72 @@ ++#ifndef foomainvolumepolicyhfoo ++#define foomainvolumepolicyhfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include ++#include ++ ++#include ++ ++typedef struct pa_main_volume_policy pa_main_volume_policy; ++ ++/* Avoid circular dependencies... */ ++typedef struct pa_main_volume_context pa_main_volume_context; ++ ++enum { ++ PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_PUT, ++ PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK, ++ PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED, ++ PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_VOLUME_CONTROL_CHANGED, ++ PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_MUTE_CONTROL_CHANGED, ++ PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_MUTE_CONTROL_CHANGED, ++ PA_MAIN_VOLUME_POLICY_HOOK_ACTIVE_MAIN_VOLUME_CONTEXT_CHANGED, ++ PA_MAIN_VOLUME_POLICY_HOOK_MAX, ++}; ++ ++struct pa_main_volume_policy { ++ pa_core *core; ++ unsigned refcnt; ++ pa_volume_api *volume_api; ++ pa_hashmap *names; /* object name -> object name (hashmap-as-a-set) */ ++ pa_hashmap *main_volume_contexts; /* name -> pa_main_volume_context */ ++ pa_main_volume_context *active_main_volume_context; ++ ++ uint32_t next_main_volume_context_index; ++ pa_hook hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAX]; ++ pa_binding_target_type *main_volume_context_binding_target_type; ++}; ++ ++pa_main_volume_policy *pa_main_volume_policy_get(pa_core *core); ++pa_main_volume_policy *pa_main_volume_policy_ref(pa_main_volume_policy *policy); ++void pa_main_volume_policy_unref(pa_main_volume_policy *policy); ++ ++int pa_main_volume_policy_register_name(pa_main_volume_policy *policy, const char *requested_name, ++ bool fail_if_already_registered, const char **registered_name); ++void pa_main_volume_policy_unregister_name(pa_main_volume_policy *policy, const char *name); ++ ++uint32_t pa_main_volume_policy_allocate_main_volume_context_index(pa_main_volume_policy *policy); ++void pa_main_volume_policy_add_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context); ++int pa_main_volume_policy_remove_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context); ++void pa_main_volume_policy_set_active_main_volume_context(pa_main_volume_policy *policy, pa_main_volume_context *context); ++ ++#endif +diff --git a/src/modules/main-volume-policy/module-main-volume-policy.c b/src/modules/main-volume-policy/module-main-volume-policy.c +new file mode 100644 +index 0000000..a14699d +--- /dev/null ++++ b/src/modules/main-volume-policy/module-main-volume-policy.c +@@ -0,0 +1,556 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include "module-main-volume-policy-symdef.h" ++ ++#include ++ ++#include ++#include ++ ++#include ++ ++#include ++#include ++#include ++ ++PA_MODULE_AUTHOR("Tanu Kaskinen"); ++PA_MODULE_DESCRIPTION(_("Main volume and mute policy")); ++PA_MODULE_VERSION(PACKAGE_VERSION); ++PA_MODULE_LOAD_ONCE(true); ++ ++enum control_type { ++ CONTROL_TYPE_VOLUME, ++ CONTROL_TYPE_MUTE, ++}; ++ ++enum model { ++ MODEL_NONE, ++ MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT, ++}; ++ ++struct userdata { ++ pa_main_volume_policy *main_volume_policy; ++ enum model output_volume_model; ++ enum model input_volume_model; ++ enum model output_mute_model; ++ enum model input_mute_model; ++ pa_hashmap *contexts; /* name -> struct context */ ++ ++ pa_hook_slot *active_main_volume_context_changed_slot; ++ ++ /* The following fields are only used during initialization. */ ++ pa_hashmap *context_names; /* name -> name (hashmap-as-a-set) */ ++ pa_hashmap *unused_contexts; /* name -> struct context */ ++}; ++ ++struct context { ++ struct userdata *userdata; ++ char *name; ++ char *description; ++ pa_binding_target_info *main_output_volume_control_target_info; ++ pa_binding_target_info *main_input_volume_control_target_info; ++ pa_binding_target_info *main_output_mute_control_target_info; ++ pa_binding_target_info *main_input_mute_control_target_info; ++ pa_main_volume_context *main_volume_context; ++ ++ bool unlinked; ++}; ++ ++static void context_unlink(struct context *context); ++ ++static const char *model_to_string(enum model model) { ++ switch (model) { ++ case MODEL_NONE: ++ return "none"; ++ ++ case MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT: ++ return "by-active-main-volume-context"; ++ } ++ ++ pa_assert_not_reached(); ++} ++ ++static int model_from_string(const char *str, enum model *model) { ++ pa_assert(str); ++ pa_assert(model); ++ ++ if (pa_streq(str, "none")) ++ *model = MODEL_NONE; ++ else if (pa_streq(str, "by-active-main-volume-context")) ++ *model = MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT; ++ else ++ return -PA_ERR_INVALID; ++ ++ return 0; ++} ++ ++static struct context *context_new(struct userdata *u, const char *name) { ++ struct context *context; ++ ++ pa_assert(u); ++ pa_assert(name); ++ ++ context = pa_xnew0(struct context, 1); ++ context->userdata = u; ++ context->name = pa_xstrdup(name); ++ context->description = pa_xstrdup(name); ++ ++ return context; ++} ++ ++static int context_put(struct context *context) { ++ int r; ++ ++ pa_assert(context); ++ ++ r = pa_main_volume_context_new(context->userdata->main_volume_policy, context->name, context->description, ++ &context->main_volume_context); ++ if (r < 0) ++ goto fail; ++ ++ if (context->main_output_volume_control_target_info) ++ pa_main_volume_context_bind_main_output_volume_control(context->main_volume_context, ++ context->main_output_volume_control_target_info); ++ ++ if (context->main_input_volume_control_target_info) ++ pa_main_volume_context_bind_main_input_volume_control(context->main_volume_context, ++ context->main_input_volume_control_target_info); ++ ++ if (context->main_output_mute_control_target_info) ++ pa_main_volume_context_bind_main_output_mute_control(context->main_volume_context, ++ context->main_output_mute_control_target_info); ++ ++ if (context->main_input_mute_control_target_info) ++ pa_main_volume_context_bind_main_input_mute_control(context->main_volume_context, ++ context->main_input_mute_control_target_info); ++ ++ pa_main_volume_context_put(context->main_volume_context); ++ ++ return 0; ++ ++fail: ++ context_unlink(context); ++ ++ return r; ++} ++ ++static void context_unlink(struct context *context) { ++ pa_assert(context); ++ ++ if (context->unlinked) ++ return; ++ ++ context->unlinked = true; ++ ++ if (context->main_volume_context) { ++ pa_main_volume_context_free(context->main_volume_context); ++ context->main_volume_context = NULL; ++ } ++} ++ ++static void context_free(struct context *context) { ++ pa_assert(context); ++ ++ if (!context->unlinked) ++ context_unlink(context); ++ ++ if (context->main_input_mute_control_target_info) ++ pa_binding_target_info_free(context->main_input_mute_control_target_info); ++ ++ if (context->main_output_mute_control_target_info) ++ pa_binding_target_info_free(context->main_output_mute_control_target_info); ++ ++ if (context->main_input_volume_control_target_info) ++ pa_binding_target_info_free(context->main_input_volume_control_target_info); ++ ++ if (context->main_output_volume_control_target_info) ++ pa_binding_target_info_free(context->main_output_volume_control_target_info); ++ ++ pa_xfree(context->description); ++ pa_xfree(context->name); ++ pa_xfree(context); ++} ++ ++static void context_set_description(struct context *context, const char *description) { ++ pa_assert(context); ++ pa_assert(description); ++ ++ pa_xfree(context->description); ++ context->description = pa_xstrdup(description); ++} ++ ++static void context_set_main_control_target_info(struct context *context, enum control_type type, pa_direction_t direction, ++ pa_binding_target_info *info) { ++ pa_assert(context); ++ ++ switch (type) { ++ case CONTROL_TYPE_VOLUME: ++ if (direction == PA_DIRECTION_OUTPUT) { ++ if (context->main_output_volume_control_target_info) ++ pa_binding_target_info_free(context->main_output_volume_control_target_info); ++ ++ if (info) ++ context->main_output_volume_control_target_info = pa_binding_target_info_copy(info); ++ else ++ context->main_output_volume_control_target_info = NULL; ++ } else { ++ if (context->main_input_volume_control_target_info) ++ pa_binding_target_info_free(context->main_input_volume_control_target_info); ++ ++ if (info) ++ context->main_input_volume_control_target_info = pa_binding_target_info_copy(info); ++ else ++ context->main_input_volume_control_target_info = NULL; ++ } ++ break; ++ ++ case CONTROL_TYPE_MUTE: ++ if (direction == PA_DIRECTION_OUTPUT) { ++ if (context->main_output_mute_control_target_info) ++ pa_binding_target_info_free(context->main_output_mute_control_target_info); ++ ++ if (info) ++ context->main_output_mute_control_target_info = pa_binding_target_info_copy(info); ++ else ++ context->main_output_mute_control_target_info = NULL; ++ } else { ++ if (context->main_input_mute_control_target_info) ++ pa_binding_target_info_free(context->main_input_mute_control_target_info); ++ ++ if (info) ++ context->main_input_mute_control_target_info = pa_binding_target_info_copy(info); ++ else ++ context->main_input_mute_control_target_info = NULL; ++ } ++ break; ++ } ++} ++ ++static pa_hook_result_t active_main_volume_context_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct userdata *u = userdata; ++ pa_main_volume_context *context; ++ pa_volume_api *api; ++ pa_binding_target_info *info = NULL; ++ ++ pa_assert(u); ++ ++ context = u->main_volume_policy->active_main_volume_context; ++ api = u->main_volume_policy->volume_api; ++ ++ if (u->output_volume_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) { ++ if (context) { ++ info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name, ++ PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL); ++ pa_volume_api_bind_main_output_volume_control(api, info); ++ } else ++ pa_volume_api_set_main_output_volume_control(api, NULL); ++ } ++ ++ if (u->input_volume_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) { ++ if (context) { ++ info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name, ++ PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL); ++ pa_volume_api_bind_main_input_volume_control(api, info); ++ } else ++ pa_volume_api_set_main_input_volume_control(api, NULL); ++ } ++ ++ if (u->output_mute_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) { ++ if (context) { ++ info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name, ++ PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL); ++ pa_volume_api_bind_main_output_mute_control(api, info); ++ } else ++ pa_volume_api_set_main_output_mute_control(api, NULL); ++ } ++ ++ if (u->input_mute_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) { ++ if (context) { ++ info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name, ++ PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL); ++ pa_volume_api_bind_main_input_mute_control(api, info); ++ } else ++ pa_volume_api_set_main_input_mute_control(api, NULL); ++ } ++ ++ if (info) ++ pa_binding_target_info_free(info); ++ ++ return PA_HOOK_OK; ++} ++ ++static int parse_model(pa_config_parser_state *state) { ++ int r; ++ ++ pa_assert(state); ++ ++ r = model_from_string(state->rvalue, state->data); ++ if (r < 0) ++ pa_log("[%s:%u] Failed to parse model: %s", state->filename, state->lineno, state->rvalue); ++ ++ return r; ++} ++ ++static int parse_main_volume_contexts(pa_config_parser_state *state) { ++ struct userdata *u; ++ char *name; ++ const char *split_state = NULL; ++ ++ pa_assert(state); ++ ++ u = state->userdata; ++ ++ while ((name = pa_split_spaces(state->rvalue, &split_state))) ++ pa_hashmap_put(u->context_names, name, name); ++ ++ return 0; ++} ++ ++static struct context *get_context(struct userdata *u, const char *section) { ++ const char *name; ++ struct context *context; ++ ++ pa_assert(u); ++ ++ if (!section) ++ return NULL; ++ ++ if (!pa_startswith(section, "MainVolumeContext ")) ++ return NULL; ++ ++ name = section + 18; ++ ++ context = pa_hashmap_get(u->unused_contexts, name); ++ if (!context) { ++ context = context_new(u, name); ++ pa_hashmap_put(u->unused_contexts, context->name, context); ++ } ++ ++ return context; ++} ++ ++static int parse_description(pa_config_parser_state *state) { ++ struct userdata *u; ++ struct context *context; ++ ++ pa_assert(state); ++ ++ u = state->userdata; ++ ++ context = get_context(u, state->section); ++ if (!context) { ++ pa_log("[%s:%u] Key \"%s\" not expected in section %s.", state->filename, state->lineno, state->lvalue, ++ pa_strnull(state->section)); ++ return -PA_ERR_INVALID; ++ } ++ ++ context_set_description(context, state->rvalue); ++ ++ return 0; ++} ++ ++static const char *get_target_field_name(enum control_type type) { ++ switch (type) { ++ case CONTROL_TYPE_VOLUME: ++ return "volume_control"; ++ ++ case CONTROL_TYPE_MUTE: ++ return "mute_control"; ++ } ++ ++ pa_assert_not_reached(); ++} ++ ++static int parse_main_control(pa_config_parser_state *state, enum control_type type, pa_direction_t direction) { ++ struct userdata *u; ++ struct context *context; ++ ++ pa_assert(state); ++ ++ u = state->userdata; ++ ++ context = get_context(u, state->section); ++ if (!context) { ++ pa_log("[%s:%u] Key \"%s\" not expected in section %s.", state->filename, state->lineno, state->lvalue, ++ pa_strnull(state->section)); ++ return -PA_ERR_INVALID; ++ } ++ ++ if (pa_streq(state->rvalue, "none")) ++ context_set_main_control_target_info(context, type, direction, NULL); ++ else if (pa_startswith(state->rvalue, "bind:")) { ++ int r; ++ pa_binding_target_info *info; ++ ++ r = pa_binding_target_info_new_from_string(state->rvalue, get_target_field_name(type), &info); ++ if (r < 0) { ++ pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue); ++ return r; ++ } ++ ++ context_set_main_control_target_info(context, type, direction, info); ++ pa_binding_target_info_free(info); ++ } else { ++ pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue); ++ return -PA_ERR_INVALID; ++ } ++ ++ return 0; ++} ++ ++static int parse_main_output_volume_control(pa_config_parser_state *state) { ++ pa_assert(state); ++ ++ return parse_main_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT); ++} ++ ++static int parse_main_input_volume_control(pa_config_parser_state *state) { ++ pa_assert(state); ++ ++ return parse_main_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT); ++} ++ ++static int parse_main_output_mute_control(pa_config_parser_state *state) { ++ pa_assert(state); ++ ++ return parse_main_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT); ++} ++ ++static int parse_main_input_mute_control(pa_config_parser_state *state) { ++ pa_assert(state); ++ ++ return parse_main_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT); ++} ++ ++static void finalize_config(struct userdata *u) { ++ const char *context_name; ++ void *state; ++ struct context *context; ++ ++ pa_assert(u); ++ ++ PA_HASHMAP_FOREACH(context_name, u->context_names, state) { ++ int r; ++ ++ context = pa_hashmap_remove(u->unused_contexts, context_name); ++ if (!context) ++ context = context_new(u, context_name); ++ ++ r = context_put(context); ++ if (r < 0) { ++ pa_log_warn("Failed to create main volume context %s.", context_name); ++ context_free(context); ++ continue; ++ } ++ ++ pa_assert_se(pa_hashmap_put(u->contexts, context->name, context) >= 0); ++ } ++ ++ PA_HASHMAP_FOREACH(context, u->unused_contexts, state) ++ pa_log_debug("Main volume context %s is not used.", context->name); ++ ++ pa_hashmap_free(u->unused_contexts); ++ u->unused_contexts = NULL; ++ ++ pa_hashmap_free(u->context_names); ++ u->context_names = NULL; ++} ++ ++int pa__init(pa_module *module) { ++ struct userdata *u; ++ FILE *f; ++ char *fn = NULL; ++ ++ pa_assert(module); ++ ++ u = module->userdata = pa_xnew0(struct userdata, 1); ++ u->main_volume_policy = pa_main_volume_policy_get(module->core); ++ u->output_volume_model = MODEL_NONE; ++ u->input_volume_model = MODEL_NONE; ++ u->output_mute_model = MODEL_NONE; ++ u->input_mute_model = MODEL_NONE; ++ u->contexts = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, ++ (pa_free_cb_t) context_free); ++ u->active_main_volume_context_changed_slot = ++ pa_hook_connect(&u->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_ACTIVE_MAIN_VOLUME_CONTEXT_CHANGED], ++ PA_HOOK_NORMAL, active_main_volume_context_changed_cb, u); ++ u->context_names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree); ++ u->unused_contexts = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, ++ (pa_free_cb_t) context_free); ++ ++ f = pa_open_config_file(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "main-volume-policy.conf", "main-volume-policy.conf", NULL, &fn); ++ if (f) { ++ pa_config_item config_items[] = { ++ { "output-volume-model", parse_model, &u->output_volume_model, "General" }, ++ { "input-volume-model", parse_model, &u->input_volume_model, "General" }, ++ { "output-mute-model", parse_model, &u->output_mute_model, "General" }, ++ { "input-mute-model", parse_model, &u->input_mute_model, "General" }, ++ { "main-volume-contexts", parse_main_volume_contexts, NULL, "General" }, ++ { "description", parse_description, NULL, NULL }, ++ { "main-output-volume-control", parse_main_output_volume_control, NULL, NULL }, ++ { "main-input-volume-control", parse_main_input_volume_control, NULL, NULL }, ++ { "main-output-mute-control", parse_main_output_mute_control, NULL, NULL }, ++ { "main-input-mute-control", parse_main_input_mute_control, NULL, NULL }, ++ { NULL }, ++ }; ++ ++ pa_config_parse(fn, f, config_items, NULL, u); ++ pa_xfree(fn); ++ fn = NULL; ++ fclose(f); ++ f = NULL; ++ } ++ ++ finalize_config(u); ++ ++ pa_log_debug("Output volume model: %s", model_to_string(u->output_volume_model)); ++ pa_log_debug("Input volume model: %s", model_to_string(u->input_volume_model)); ++ pa_log_debug("Output mute model: %s", model_to_string(u->output_mute_model)); ++ pa_log_debug("Input mute model: %s", model_to_string(u->input_mute_model)); ++ ++ return 0; ++} ++ ++void pa__done(pa_module *module) { ++ struct userdata *u; ++ ++ pa_assert(module); ++ ++ u = module->userdata; ++ if (!u) ++ return; ++ ++ if (u->active_main_volume_context_changed_slot) ++ pa_hook_slot_free(u->active_main_volume_context_changed_slot); ++ ++ if (u->contexts) ++ pa_hashmap_free(u->contexts); ++ ++ if (u->main_volume_policy) ++ pa_main_volume_policy_unref(u->main_volume_policy); ++ ++ pa_xfree(u); ++} +-- +2.1.4 + +--- a/po/POTFILES.in 2016-04-14 13:03:50.715006116 +0200 ++++ b/po/POTFILES.in 2016-04-14 13:04:23.097006062 +0200 +@@ -200,3 +200,4 @@ + src/utils/pax11publish.c + src/modules/volume-api/device-creator.c + src/pulse/ext-volume-api.c ++src/modules/main-volume-policy/module-main-volume-policy.c diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0039-main-volume-policy-adapt-to-pa6rev.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0039-main-volume-policy-adapt-to-pa6rev.patch new file mode 100644 index 000000000..4f2777112 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0039-main-volume-policy-adapt-to-pa6rev.patch @@ -0,0 +1,11 @@ +--- a/src/modules/main-volume-policy/module-main-volume-policy.c 2016-04-18 10:59:46.124086205 +0200 ++++ b/src/modules/main-volume-policy/module-main-volume-policy.c 2016-04-18 11:01:31.497089360 +0200 +@@ -517,7 +517,7 @@ + { NULL }, + }; + +- pa_config_parse(fn, f, config_items, NULL, u); ++ pa_config_parse(fn, f, config_items, NULL, false, u); + pa_xfree(fn); + fn = NULL; + fclose(f); diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio_%.bbappend b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio_%.bbappend new file mode 100644 index 000000000..a54ec60f1 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio_%.bbappend @@ -0,0 +1,28 @@ +FILESEXTRAPATHS_append := ":${THISDIR}/${PN}" + +inherit systemd + +do_install_append() { + # Install pulseaudio systemd service + if ${@bb.utils.contains('DISTRO_FEATURES', 'systemd', 'true', 'false', d)}; then + install -m 644 -p -D ${WORKDIR}/build/src/pulseaudio.service ${D}${systemd_user_unitdir}/pulseaudio.service + install -m 644 -p -D ${WORKDIR}/pulseaudio-${PV}/src/daemon/systemd/user/pulseaudio.socket ${D}${systemd_user_unitdir}/pulseaudio.socket + + # Execute these manually on behalf of systemctl script (from systemd-systemctl-native.bb) + # because it does not support systemd's user mode. + install -d ${D}${systemd_user_unitdir}/sockets.target.wants/ + ln -sf ${systemd_user_unitdir}/pulseaudio.socket ${D}${systemd_user_unitdir}/sockets.target.wants/ + + install -d ${D}${systemd_user_unitdir}/default.target.wants/ + ln -sf ${systemd_user_unitdir}/pulseaudio.service ${D}${systemd_user_unitdir}/default.target.wants/ + fi + mkdir -p ${D}/${bindir} + install -m 755 -p -D ${WORKDIR}/build/src/.libs/pacat ${D}/${bindir}/ +} + +FILES_${PN}-server += " \ + ${@bb.utils.contains('DISTRO_FEATURES', 'systemd', '${systemd_user_unitdir}/pulseaudio.socket', '', d)} \ + ${@bb.utils.contains('DISTRO_FEATURES', 'systemd', '${systemd_user_unitdir}/sockets.target.wants/pulseaudio.socket', '', d)} \ + ${@bb.utils.contains('DISTRO_FEATURES', 'systemd', '${systemd_user_unitdir}/pulseaudio.service', '', d)} \ + ${@bb.utils.contains('DISTRO_FEATURES', 'systemd', '${systemd_user_unitdir}/default.target.wants/pulseaudio.service', '', d)} \ +" diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio_10.0.bbappend b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio_10.0.bbappend new file mode 100644 index 000000000..f59ee89d5 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio_10.0.bbappend @@ -0,0 +1,15 @@ +FILESEXTRAPATHS_prepend := "${THISDIR}/pulseaudio-10.0:" + +SRC_URI += " \ + file://0001-install-files-for-a-module-development.patch \ + file://0002-volume-ramp-additions-to-the-low-level-infra.patch \ + file://0003-volume-ramp-adding-volume-ramping-to-sink-input.patch \ + file://0004-sink-input-Code-cleanup-regarding-volume-ramping.patch \ + file://0005-sink-input-volume-Add-support-for-volume-ramp-factor.patch \ + file://0006-sink-input-Remove-pa_sink_input_set_volume_ramp.patch;apply=no \ + file://enable-ofono-hfp-backend.patch \ +" + +PACKAGES =+ " pulseaudio-module-dev" + +FILES_pulseaudio-module-dev = "${includedir}/pulsemodule/* ${libdir}/pkgconfig/pulseaudio-module-devel.pc" -- cgit 1.2.3-korg