summaryrefslogtreecommitdiffstats
path: root/src/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/alsa/alsa-mixer.c85
-rw-r--r--src/modules/alsa/alsa-mixer.h2
-rw-r--r--src/modules/alsa/alsa-sink.c299
-rw-r--r--src/modules/alsa/alsa-source.c280
-rw-r--r--src/modules/alsa/alsa-util.c283
-rw-r--r--src/modules/alsa/alsa-util.h15
-rw-r--r--src/modules/alsa/mixer/paths/analog-input.conf.common39
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-headphones-2.conf82
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-headphones.conf9
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf13
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-mono.conf13
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-speaker.conf94
-rw-r--r--src/modules/alsa/mixer/paths/analog-output.conf20
-rw-r--r--src/modules/alsa/mixer/paths/analog-output.conf.common19
-rw-r--r--src/modules/alsa/mixer/profile-sets/default.conf14
-rw-r--r--src/modules/alsa/module-alsa-card.c2
-rw-r--r--src/modules/bluetooth/bluetooth-util.c71
-rw-r--r--src/modules/bluetooth/bluetooth-util.h10
-rw-r--r--src/modules/bluetooth/module-bluetooth-device.c246
-rw-r--r--src/modules/bluetooth/module-bluetooth-discover.c7
-rw-r--r--src/modules/dbus/iface-card-profile.c228
-rw-r--r--src/modules/dbus/iface-card-profile.h50
-rw-r--r--src/modules/dbus/iface-card.c561
-rw-r--r--src/modules/dbus/iface-card.h45
-rw-r--r--src/modules/dbus/iface-client.c459
-rw-r--r--src/modules/dbus/iface-client.h45
-rw-r--r--src/modules/dbus/iface-core.c2197
-rw-r--r--src/modules/dbus/iface-core.h52
-rw-r--r--src/modules/dbus/iface-device-port.c190
-rw-r--r--src/modules/dbus/iface-device-port.h50
-rw-r--r--src/modules/dbus/iface-device.c1316
-rw-r--r--src/modules/dbus/iface-device.h53
-rw-r--r--src/modules/dbus/iface-memstats.c231
-rw-r--r--src/modules/dbus/iface-memstats.h45
-rw-r--r--src/modules/dbus/iface-module.c336
-rw-r--r--src/modules/dbus/iface-module.h45
-rw-r--r--src/modules/dbus/iface-sample.c519
-rw-r--r--src/modules/dbus/iface-sample.h45
-rw-r--r--src/modules/dbus/iface-stream.c894
-rw-r--r--src/modules/dbus/iface-stream.h47
-rw-r--r--src/modules/dbus/module-dbus-protocol.c607
-rw-r--r--src/modules/gconf/module-gconf.c9
-rw-r--r--src/modules/hal-util.c2
-rw-r--r--src/modules/jack/module-jack-sink.c2
-rw-r--r--src/modules/jack/module-jack-source.c2
-rw-r--r--src/modules/module-always-sink.c6
-rw-r--r--src/modules/module-cli.c28
-rw-r--r--src/modules/module-combine.c572
-rw-r--r--src/modules/module-console-kit.c2
-rw-r--r--src/modules/module-default-device-restore.c8
-rw-r--r--src/modules/module-detect.c14
-rw-r--r--src/modules/module-device-manager.c1540
-rw-r--r--src/modules/module-device-restore.c2
-rw-r--r--src/modules/module-equalizer-sink.c2161
-rw-r--r--src/modules/module-hal-detect-compat.c84
-rw-r--r--src/modules/module-hal-detect.c33
-rw-r--r--src/modules/module-intended-roles.c54
-rw-r--r--src/modules/module-ladspa-sink.c239
-rw-r--r--src/modules/module-lirc.c46
-rw-r--r--src/modules/module-loopback.c784
-rw-r--r--src/modules/module-match.c7
-rw-r--r--src/modules/module-mmkbd-evdev.c69
-rw-r--r--src/modules/module-null-sink.c5
-rw-r--r--src/modules/module-pipe-sink.c3
-rw-r--r--src/modules/module-pipe-source.c3
-rw-r--r--src/modules/module-position-event-sounds.c72
-rw-r--r--src/modules/module-remap-sink.c168
-rw-r--r--src/modules/module-rescue-streams.c179
-rw-r--r--src/modules/module-sine.c2
-rw-r--r--src/modules/module-solaris.c106
-rw-r--r--src/modules/module-stream-restore.c1144
-rw-r--r--src/modules/module-suspend-on-idle.c30
-rw-r--r--src/modules/module-tunnel.c77
-rw-r--r--src/modules/module-udev-detect.c357
-rw-r--r--src/modules/module-volume-restore.c9
-rw-r--r--src/modules/oss/module-oss.c16
-rw-r--r--src/modules/oss/oss-util.c18
-rw-r--r--src/modules/raop/base64.c1
-rw-r--r--src/modules/raop/module-raop-discover.c4
-rw-r--r--src/modules/raop/module-raop-sink.c6
-rw-r--r--src/modules/reserve-monitor.c51
-rw-r--r--src/modules/reserve-wrap.c10
-rw-r--r--src/modules/reserve.c25
-rw-r--r--src/modules/rtp/module-rtp-recv.c7
-rw-r--r--src/modules/rtp/module-rtp-send.c9
-rw-r--r--src/modules/rtp/rtsp_client.c32
-rw-r--r--src/modules/x11/module-x11-publish.c2
87 files changed, 16431 insertions, 1187 deletions
diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 6a0b4ab7..8b13239c 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -479,7 +479,6 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
snd_mixer_elem_t *me;
snd_mixer_selem_channel_id_t c;
pa_channel_position_mask_t mask = 0;
- pa_volume_t max_channel_volume = PA_VOLUME_MUTED;
unsigned k;
pa_assert(m);
@@ -546,9 +545,6 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
f = from_alsa_volume(value, e->min_volume, e->max_volume);
}
- if (f > max_channel_volume)
- max_channel_volume = f;
-
for (k = 0; k < cm->channels; k++)
if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k]))
if (v->values[k] < f)
@@ -559,7 +555,7 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
for (k = 0; k < cm->channels; k++)
if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k])))
- v->values[k] = max_channel_volume;
+ v->values[k] = PA_VOLUME_NORM;
return 0;
}
@@ -681,7 +677,6 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
snd_mixer_elem_t *me;
snd_mixer_selem_channel_id_t c;
pa_channel_position_mask_t mask = 0;
- pa_volume_t max_channel_volume = PA_VOLUME_MUTED;
unsigned k;
pa_assert(m);
@@ -771,9 +766,6 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
f = from_alsa_volume(value, e->min_volume, e->max_volume);
}
- if (f > max_channel_volume)
- max_channel_volume = f;
-
for (k = 0; k < cm->channels; k++)
if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k]))
if (rv.values[k] < f)
@@ -784,7 +776,7 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
for (k = 0; k < cm->channels; k++)
if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k])))
- rv.values[k] = max_channel_volume;
+ rv.values[k] = PA_VOLUME_NORM;
*v = rv;
return 0;
@@ -929,7 +921,7 @@ static int element_zero_volume(pa_alsa_element *e, snd_mixer_t *m) {
int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) {
pa_alsa_element *e;
- int r;
+ int r = 0;
pa_assert(m);
pa_assert(p);
@@ -1716,11 +1708,13 @@ static int option_verify(pa_alsa_option *o) {
{ "input-radio", N_("Radio") },
{ "input-video", N_("Video") },
{ "input-agc-on", N_("Automatic Gain Control") },
- { "input-agc-off", "" },
+ { "input-agc-off", N_("No Automatic Gain Control") },
{ "input-boost-on", N_("Boost") },
- { "input-boost-off", "" },
+ { "input-boost-off", N_("No Boost") },
{ "output-amplifier-on", N_("Amplifier") },
- { "output-amplifier-off", "" }
+ { "output-amplifier-off", N_("No Amplifier") },
+ { "output-speaker", N_("Speaker") },
+ { "output-headphones", N_("Headphones") }
};
pa_assert(o);
@@ -1778,15 +1772,17 @@ static int element_verify(pa_alsa_element *e) {
static int path_verify(pa_alsa_path *p) {
static const struct description_map well_known_descriptions[] = {
- { "analog-input", N_("Analog Input") },
- { "analog-input-microphone", N_("Analog Microphone") },
- { "analog-input-linein", N_("Analog Line-In") },
- { "analog-input-radio", N_("Analog Radio") },
- { "analog-input-video", N_("Analog Video") },
- { "analog-output", N_("Analog Output") },
- { "analog-output-headphones", N_("Analog Headphones") },
- { "analog-output-lfe-on-mono", N_("Analog Output (LFE)") },
- { "analog-output-mono", N_("Analog Mono Output") }
+ { "analog-input", N_("Analog Input") },
+ { "analog-input-microphone", N_("Analog Microphone") },
+ { "analog-input-linein", N_("Analog Line-In") },
+ { "analog-input-radio", N_("Analog Radio") },
+ { "analog-input-video", N_("Analog Video") },
+ { "analog-output", N_("Analog Output") },
+ { "analog-output-headphones", N_("Analog Headphones") },
+ { "analog-output-lfe-on-mono", N_("Analog Output (LFE)") },
+ { "analog-output-mono", N_("Analog Mono Output") },
+ { "analog-output-headphones-2", N_("Analog Headphones 2") },
+ { "analog-output-speaker", N_("Analog Speaker") }
};
pa_alsa_element *e;
@@ -1849,7 +1845,12 @@ pa_alsa_path* pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction)
items[1].data = &p->description;
items[2].data = &p->name;
- fn = pa_maybe_prefix_path(fname, PA_ALSA_PATHS_DIR);
+ fn = pa_maybe_prefix_path(fname,
+#if defined(__linux__) && !defined(__OPTIMIZE__)
+ pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/paths/" :
+#endif
+ PA_ALSA_PATHS_DIR);
+
r = pa_config_parse(fn, NULL, items, p);
pa_xfree(fn);
@@ -2838,9 +2839,9 @@ static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) {
if (bonus) {
if (pa_channel_map_equal(&m->channel_map, bonus))
- m->priority += 5000;
+ m->priority += 50;
else if (m->channel_map.channels == bonus->channels)
- m->priority += 4000;
+ m->priority += 30;
}
return 0;
@@ -2884,7 +2885,7 @@ static void profile_set_add_auto_pair(
else
name = pa_sprintf_malloc("input:%s", n->name);
- if ((p = pa_hashmap_get(ps->profiles, name))) {
+ if (pa_hashmap_get(ps->profiles, name)) {
pa_xfree(name);
return;
}
@@ -3110,7 +3111,12 @@ pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel
if (!fname)
fname = "default.conf";
- fn = pa_maybe_prefix_path(fname, PA_ALSA_PROFILE_SETS_DIR);
+ fn = pa_maybe_prefix_path(fname,
+#if defined(__linux__) && !defined(__OPTIMIZE__)
+ pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/profile-sets/" :
+#endif
+ PA_ALSA_PROFILE_SETS_DIR);
+
r = pa_config_parse(fn, NULL, items, ps);
pa_xfree(fn);
@@ -3135,7 +3141,13 @@ fail:
return NULL;
}
-void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss) {
+void pa_alsa_profile_set_probe(
+ pa_alsa_profile_set *ps,
+ const char *dev_id,
+ const pa_sample_spec *ss,
+ unsigned default_n_fragments,
+ unsigned default_fragment_size_msec) {
+
void *state;
pa_alsa_profile *p, *last = NULL;
pa_alsa_mapping *m;
@@ -3150,6 +3162,7 @@ void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, cons
PA_HASHMAP_FOREACH(p, ps->profiles, state) {
pa_sample_spec try_ss;
pa_channel_map try_map;
+ snd_pcm_uframes_t try_period_size, try_buffer_size;
uint32_t idx;
/* Is this already marked that it is supported? (i.e. from the config file) */
@@ -3203,13 +3216,18 @@ void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, cons
try_ss = *ss;
try_ss.channels = try_map.channels;
+ try_period_size =
+ pa_usec_to_bytes(default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
+ pa_frame_size(&try_ss);
+ try_buffer_size = default_n_fragments * try_period_size;
+
if (!(m ->output_pcm = pa_alsa_open_by_template(
m->device_strings,
dev_id,
NULL,
&try_ss, &try_map,
SND_PCM_STREAM_PLAYBACK,
- NULL, NULL, 0, NULL, NULL,
+ &try_period_size, &try_buffer_size, 0, NULL, NULL,
TRUE))) {
p->supported = FALSE;
break;
@@ -3227,13 +3245,18 @@ void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, cons
try_ss = *ss;
try_ss.channels = try_map.channels;
+ try_period_size =
+ pa_usec_to_bytes(default_fragment_size_msec*PA_USEC_PER_MSEC, &try_ss) /
+ pa_frame_size(&try_ss);
+ try_buffer_size = default_n_fragments * try_period_size;
+
if (!(m ->input_pcm = pa_alsa_open_by_template(
m->device_strings,
dev_id,
NULL,
&try_ss, &try_map,
SND_PCM_STREAM_CAPTURE,
- NULL, NULL, 0, NULL, NULL,
+ &try_period_size, &try_buffer_size, 0, NULL, NULL,
TRUE))) {
p->supported = FALSE;
break;
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index 76788183..a0d4fcbe 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -269,7 +269,7 @@ void pa_alsa_mapping_dump(pa_alsa_mapping *m);
void pa_alsa_profile_dump(pa_alsa_profile *p);
pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus);
-void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss);
+void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec);
void pa_alsa_profile_set_free(pa_alsa_profile_set *s);
void pa_alsa_profile_set_dump(pa_alsa_profile_set *s);
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 1c38430f..856adb14 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -62,11 +62,26 @@
/* #define DEBUG_TIMING */
#define DEFAULT_DEVICE "default"
-#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s -- Overall buffer size */
-#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms -- Fill up when only this much is left in the buffer */
-#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- On underrun, increase watermark by this */
-#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- Sleep at least 10ms on each iteration */
-#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms -- Wakeup at least this long before the buffer runs empty*/
+
+#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s -- Overall buffer size */
+#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms -- Fill up when only this much is left in the buffer */
+
+#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- On underrun, increase watermark by this */
+#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms -- When everything's great, decrease watermark by this */
+#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s -- How long after a drop out recheck if things are good now */
+#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (0*PA_USEC_PER_MSEC) /* 0ms -- If the buffer level ever below this theshold, increase the watermark */
+#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms -- If the buffer level didn't drop below this theshold in the verification time, decrease the watermark */
+
+/* Note that TSCHED_WATERMARK_INC_THRESHOLD_USEC == 0 means tht we
+ * will increase the watermark only if we hit a real underrun. */
+
+#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- Sleep at least 10ms on each iteration */
+#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms -- Wakeup at least this long before the buffer runs empty*/
+
+#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms -- min smoother update interval */
+#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms -- max smoother update inteval */
+
+#define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */
struct userdata {
pa_core *core;
@@ -94,9 +109,13 @@ struct userdata {
hwbuf_unused,
min_sleep,
min_wakeup,
- watermark_step;
+ watermark_inc_step,
+ watermark_dec_step,
+ watermark_inc_threshold,
+ watermark_dec_threshold;
+
+ pa_usec_t watermark_dec_not_before;
- unsigned nfragments;
pa_memchunk memchunk;
char *device_name; /* name of the PCM device */
@@ -113,6 +132,8 @@ struct userdata {
pa_smoother *smoother;
uint64_t write_count;
uint64_t since_start;
+ pa_usec_t smoother_interval;
+ pa_usec_t last_smoother_update;
pa_reserve_wrapper *reserve;
pa_hook_slot *reserve_slot;
@@ -241,6 +262,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) {
size_t max_use, max_use_2;
pa_assert(u);
+ pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
max_use_2 = pa_frame_align(max_use/2, &u->sink->sample_spec);
@@ -255,6 +277,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) {
static void fix_tsched_watermark(struct userdata *u) {
size_t max_use;
pa_assert(u);
+ pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
@@ -265,7 +288,7 @@ static void fix_tsched_watermark(struct userdata *u) {
u->tsched_watermark = u->min_wakeup;
}
-static void adjust_after_underrun(struct userdata *u) {
+static void increase_watermark(struct userdata *u) {
size_t old_watermark;
pa_usec_t old_min_latency, new_min_latency;
@@ -274,31 +297,64 @@ static void adjust_after_underrun(struct userdata *u) {
/* First, just try to increase the watermark */
old_watermark = u->tsched_watermark;
- u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_step);
+ u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step);
fix_tsched_watermark(u);
if (old_watermark != u->tsched_watermark) {
- pa_log_notice("Increasing wakeup watermark to %0.2f ms",
- (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
+ pa_log_info("Increasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
return;
}
/* Hmm, we cannot increase the watermark any further, hence let's raise the latency */
old_min_latency = u->sink->thread_info.min_latency;
- new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_STEP_USEC);
+ new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC);
new_min_latency = PA_MIN(new_min_latency, u->sink->thread_info.max_latency);
if (old_min_latency != new_min_latency) {
- pa_log_notice("Increasing minimal latency to %0.2f ms",
- (double) new_min_latency / PA_USEC_PER_MSEC);
+ pa_log_info("Increasing minimal latency to %0.2f ms",
+ (double) new_min_latency / PA_USEC_PER_MSEC);
pa_sink_set_latency_range_within_thread(u->sink, new_min_latency, u->sink->thread_info.max_latency);
- return;
}
/* When we reach this we're officialy fucked! */
}
+static void decrease_watermark(struct userdata *u) {
+ size_t old_watermark;
+ pa_usec_t now;
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ now = pa_rtclock_now();
+
+ if (u->watermark_dec_not_before <= 0)
+ goto restart;
+
+ if (u->watermark_dec_not_before > now)
+ return;
+
+ old_watermark = u->tsched_watermark;
+
+ if (u->tsched_watermark < u->watermark_dec_step)
+ u->tsched_watermark = u->tsched_watermark / 2;
+ else
+ u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step);
+
+ fix_tsched_watermark(u);
+
+ if (old_watermark != u->tsched_watermark)
+ pa_log_info("Decreasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
+
+ /* We don't change the latency range*/
+
+restart:
+ u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC;
+}
+
static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
pa_usec_t usec, wm;
@@ -306,6 +362,7 @@ static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*p
pa_assert(process_usec);
pa_assert(u);
+ pa_assert(u->use_tsched);
usec = pa_sink_get_requested_latency_within_thread(u->sink);
@@ -353,42 +410,65 @@ static int try_recover(struct userdata *u, const char *call, int err) {
return 0;
}
-static size_t check_left_to_play(struct userdata *u, size_t n_bytes) {
+static size_t check_left_to_play(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) {
size_t left_to_play;
+ pa_bool_t underrun = FALSE;
/* We use <= instead of < for this check here because an underrun
* only happens after the last sample was processed, not already when
* it is removed from the buffer. This is particularly important
* when block transfer is used. */
- if (n_bytes <= u->hwbuf_size) {
+ if (n_bytes <= u->hwbuf_size)
left_to_play = u->hwbuf_size - n_bytes;
+ else {
+
+ /* We got a dropout. What a mess! */
+ left_to_play = 0;
+ underrun = TRUE;
#ifdef DEBUG_TIMING
- pa_log_debug("%0.2f ms left to play", (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
+ PA_DEBUG_TRAP;
#endif
- } else {
- left_to_play = 0;
+ if (!u->first && !u->after_rewind)
+ if (pa_log_ratelimit())
+ pa_log_info("Underrun!");
+ }
#ifdef DEBUG_TIMING
- PA_DEBUG_TRAP;
+ pa_log_debug("%0.2f ms left to play; inc threshold = %0.2f ms; dec threshold = %0.2f ms",
+ (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC,
+ (double) pa_bytes_to_usec(u->watermark_inc_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC,
+ (double) pa_bytes_to_usec(u->watermark_dec_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
#endif
+ if (u->use_tsched) {
+ pa_bool_t reset_not_before = TRUE;
+
if (!u->first && !u->after_rewind) {
+ if (underrun || left_to_play < u->watermark_inc_threshold)
+ increase_watermark(u);
+ else if (left_to_play > u->watermark_dec_threshold) {
+ reset_not_before = FALSE;
- if (pa_log_ratelimit())
- pa_log_info("Underrun!");
+ /* We decrease the watermark only if have actually
+ * been woken up by a timeout. If something else woke
+ * us up it's too easy to fulfill the deadlines... */
- if (u->use_tsched)
- adjust_after_underrun(u);
+ if (on_timeout)
+ decrease_watermark(u);
+ }
}
+
+ if (reset_not_before)
+ u->watermark_dec_not_before = 0;
}
return left_to_play;
}
-static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
+static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
pa_bool_t work_done = TRUE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_play;
@@ -423,7 +503,8 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
pa_log_debug("avail: %lu", (unsigned long) n_bytes);
#endif
- left_to_play = check_left_to_play(u, n_bytes);
+ left_to_play = check_left_to_play(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
if (u->use_tsched)
@@ -558,7 +639,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
return work_done ? 1 : 0;
}
-static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
+static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
pa_bool_t work_done = FALSE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_play;
@@ -574,6 +655,7 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
snd_pcm_sframes_t n;
size_t n_bytes;
int r;
+ pa_bool_t after_avail = TRUE;
if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) {
@@ -584,7 +666,8 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
}
n_bytes = (size_t) n * u->frame_size;
- left_to_play = check_left_to_play(u, n_bytes);
+ left_to_play = check_left_to_play(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
if (u->use_tsched)
@@ -627,7 +710,6 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
for (;;) {
snd_pcm_sframes_t frames;
void *p;
- pa_bool_t after_avail = TRUE;
/* pa_log_debug("%lu frames to write", (unsigned long) frames); */
@@ -721,18 +803,27 @@ static void update_smoother(struct userdata *u) {
now1 = pa_timespec_load(&htstamp);
}
+ /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
+ if (now1 <= 0)
+ now1 = pa_rtclock_now();
+
+ /* check if the time since the last update is bigger than the interval */
+ if (u->last_smoother_update > 0)
+ if (u->last_smoother_update + u->smoother_interval > now1)
+ return;
+
position = (int64_t) u->write_count - ((int64_t) delay * (int64_t) u->frame_size);
if (PA_UNLIKELY(position < 0))
position = 0;
- /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
- if (now1 <= 0)
- now1 = pa_rtclock_now();
-
now2 = pa_bytes_to_usec((uint64_t) position, &u->sink->sample_spec);
pa_smoother_put(u->smoother, now1, now2);
+
+ u->last_smoother_update = now1;
+ /* exponentially increase the update interval up to the MAX limit */
+ u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL);
}
static pa_usec_t sink_get_latency(struct userdata *u) {
@@ -836,7 +927,7 @@ static int update_sw_params(struct userdata *u) {
pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min);
- if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) {
+ if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min, !u->use_tsched)) < 0) {
pa_log("Failed to set software parameters: %s", pa_alsa_strerror(err));
return err;
}
@@ -851,8 +942,7 @@ static int unsuspend(struct userdata *u) {
pa_sample_spec ss;
int err;
pa_bool_t b, d;
- unsigned nfrags;
- snd_pcm_uframes_t period_size;
+ snd_pcm_uframes_t period_size, buffer_size;
pa_assert(u);
pa_assert(!u->pcm_handle);
@@ -860,7 +950,7 @@ static int unsuspend(struct userdata *u) {
pa_log_info("Trying resume...");
if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK,
- /*SND_PCM_NONBLOCK|*/
+ SND_PCM_NONBLOCK|
SND_PCM_NO_AUTO_RESAMPLE|
SND_PCM_NO_AUTO_CHANNELS|
SND_PCM_NO_AUTO_FORMAT)) < 0) {
@@ -869,12 +959,12 @@ static int unsuspend(struct userdata *u) {
}
ss = u->sink->sample_spec;
- nfrags = u->nfragments;
period_size = u->fragment_size / u->frame_size;
+ buffer_size = u->hwbuf_size / u->frame_size;
b = u->use_mmap;
d = u->use_tsched;
- if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) {
+ if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &period_size, &buffer_size, 0, &b, &d, TRUE)) < 0) {
pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err));
goto fail;
}
@@ -889,10 +979,11 @@ static int unsuspend(struct userdata *u) {
goto fail;
}
- if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) {
- pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)",
- (unsigned long) u->nfragments, (unsigned long) u->fragment_size,
- (unsigned long) nfrags, period_size * u->frame_size);
+ if (period_size*u->frame_size != u->fragment_size ||
+ buffer_size*u->frame_size != u->hwbuf_size) {
+ pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)",
+ (unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size,
+ (unsigned long) (buffer_size*u->fragment_size), (unsigned long) (period_size*u->frame_size));
goto fail;
}
@@ -904,11 +995,12 @@ static int unsuspend(struct userdata *u) {
u->write_count = 0;
pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
+ u->last_smoother_update = 0;
u->first = TRUE;
u->since_start = 0;
-
pa_log_info("Resumed successfully...");
return 0;
@@ -919,7 +1011,7 @@ fail:
u->pcm_handle = NULL;
}
- return -1;
+ return -PA_ERR_IO;
}
/* Called from IO context */
@@ -943,28 +1035,33 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
- case PA_SINK_SUSPENDED:
+ case PA_SINK_SUSPENDED: {
+ int r;
+
pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
- if (suspend(u) < 0)
- return -1;
+ if ((r = suspend(u)) < 0)
+ return r;
break;
+ }
case PA_SINK_IDLE:
- case PA_SINK_RUNNING:
+ case PA_SINK_RUNNING: {
+ int r;
if (u->sink->thread_info.state == PA_SINK_INIT) {
if (build_pollfd(u) < 0)
- return -1;
+ return -PA_ERR_IO;
}
if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
- if (unsuspend(u) < 0)
- return -1;
+ if ((r = unsuspend(u)) < 0)
+ return r;
}
break;
+ }
case PA_SINK_UNLINKED:
case PA_SINK_INIT:
@@ -992,7 +1089,7 @@ static int sink_set_state_cb(pa_sink *s, pa_sink_state_t new_state) {
reserve_done(u);
else if (old_state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(new_state))
if (reserve_init(u, u->device_name) < 0)
- return -1;
+ return -PA_ERR_BUSY;
return 0;
}
@@ -1007,7 +1104,7 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
return 0;
if (mask & SND_CTL_EVENT_MASK_VALUE) {
- pa_sink_get_volume(u->sink, TRUE, FALSE);
+ pa_sink_get_volume(u->sink, TRUE);
pa_sink_get_mute(u->sink, TRUE);
}
@@ -1034,15 +1131,11 @@ static void sink_get_volume_cb(pa_sink *s) {
if (pa_cvolume_equal(&u->hardware_volume, &r))
return;
- s->virtual_volume = u->hardware_volume = r;
+ s->real_volume = u->hardware_volume = r;
- if (u->mixer_path->has_dB) {
- pa_cvolume reset;
-
- /* Hmm, so the hardware volume changed, let's reset our software volume */
- pa_cvolume_reset(&reset, s->sample_spec.channels);
- pa_sink_set_soft_volume(s, &reset);
- }
+ /* Hmm, so the hardware volume changed, let's reset our software volume */
+ if (u->mixer_path->has_dB)
+ pa_sink_set_soft_volume(s, NULL);
}
static void sink_set_volume_cb(pa_sink *s) {
@@ -1055,7 +1148,7 @@ static void sink_set_volume_cb(pa_sink *s) {
pa_assert(u->mixer_handle);
/* Shift up by the base volume */
- pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume);
+ pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume);
if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
return;
@@ -1066,13 +1159,26 @@ static void sink_set_volume_cb(pa_sink *s) {
u->hardware_volume = r;
if (u->mixer_path->has_dB) {
+ pa_cvolume new_soft_volume;
+ pa_bool_t accurate_enough;
/* Match exactly what the user requested by software */
- pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume);
+ pa_sw_cvolume_divide(&new_soft_volume, &s->real_volume, &u->hardware_volume);
+
+ /* If the adjustment to do in software is only minimal we
+ * can skip it. That saves us CPU at the expense of a bit of
+ * accuracy */
+ accurate_enough =
+ (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) &&
+ (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY));
- pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume));
+ pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->real_volume));
pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume));
- pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume));
+ pa_log_debug("Calculated software volume: %s (accurate-enough=%s)", pa_cvolume_snprint(t, sizeof(t), &new_soft_volume),
+ pa_yes_no(accurate_enough));
+
+ if (!accurate_enough)
+ s->soft_volume = new_soft_volume;
} else {
pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));
@@ -1080,7 +1186,7 @@ static void sink_set_volume_cb(pa_sink *s) {
/* We can't match exactly what the user requested, hence let's
* at least tell the user about it */
- s->virtual_volume = r;
+ s->real_volume = r;
}
}
@@ -1252,15 +1358,16 @@ static void thread_func(void *userdata) {
if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
int work_done;
pa_usec_t sleep_usec = 0;
+ pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll);
if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
if (process_rewind(u) < 0)
goto fail;
if (u->use_mmap)
- work_done = mmap_write(u, &sleep_usec, revents & POLLOUT);
+ work_done = mmap_write(u, &sleep_usec, revents & POLLOUT, on_timeout);
else
- work_done = unix_write(u, &sleep_usec, revents & POLLOUT);
+ work_done = unix_write(u, &sleep_usec, revents & POLLOUT, on_timeout);
if (work_done < 0)
goto fail;
@@ -1292,7 +1399,8 @@ static void thread_func(void *userdata) {
* we have filled the buffer at least once
* completely.*/
- pa_log_debug("Cutting sleep time for the initial iterations by half.");
+ if (pa_log_ratelimit())
+ pa_log_debug("Cutting sleep time for the initial iterations by half.");
sleep_usec /= 2;
}
@@ -1534,8 +1642,8 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
const char *dev_id = NULL;
pa_sample_spec ss, requested_ss;
pa_channel_map map;
- uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark;
- snd_pcm_uframes_t period_frames, tsched_frames;
+ uint32_t nfrags, frag_size, buffer_size, tsched_size, tsched_watermark;
+ snd_pcm_uframes_t period_frames, buffer_frames, tsched_frames;
size_t frame_size;
pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE;
pa_sink_new_data data;
@@ -1569,8 +1677,10 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
goto fail;
}
- hwbuf_size = frag_size * nfrags;
+ buffer_size = nfrags * frag_size;
+
period_frames = frag_size/frame_size;
+ buffer_frames = buffer_size/frame_size;
tsched_frames = tsched_size/frame_size;
if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) {
@@ -1588,10 +1698,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
goto fail;
}
- if (use_tsched && !pa_rtclock_hrtimer()) {
- pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel.");
- use_tsched = FALSE;
- }
+ use_tsched = pa_alsa_may_tsched(use_tsched);
u = pa_xnew0(struct userdata, 1);
u->core = m->core;
@@ -1610,6 +1717,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
5,
pa_rtclock_now(),
TRUE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
dev_id = pa_modargs_get_value(
ma, "device_id",
@@ -1636,7 +1744,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
&u->device_name,
&ss, &map,
SND_PCM_STREAM_PLAYBACK,
- &nfrags, &period_frames, tsched_frames,
+ &period_frames, &buffer_frames, tsched_frames,
&b, &d, mapping)))
goto fail;
@@ -1651,7 +1759,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
&u->device_name,
&ss, &map,
SND_PCM_STREAM_PLAYBACK,
- &nfrags, &period_frames, tsched_frames,
+ &period_frames, &buffer_frames, tsched_frames,
&b, &d, profile_set, &mapping)))
goto fail;
@@ -1663,7 +1771,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
&u->device_name,
&ss, &map,
SND_PCM_STREAM_PLAYBACK,
- &nfrags, &period_frames, tsched_frames,
+ &period_frames, &buffer_frames, tsched_frames,
&b, &d, FALSE)))
goto fail;
}
@@ -1689,11 +1797,6 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
u->use_tsched = use_tsched = FALSE;
}
- if (use_tsched && !pa_alsa_pcm_is_hw(u->pcm_handle)) {
- pa_log_info("Device is not a hardware device, disabling timer-based scheduling.");
- u->use_tsched = use_tsched = FALSE;
- }
-
if (u->use_mmap)
pa_log_info("Successfully enabled mmap() mode.");
@@ -1715,7 +1818,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name);
- pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags));
+ pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (buffer_frames * frame_size));
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size));
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial"));
@@ -1756,21 +1859,28 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
pa_sink_set_rtpoll(u->sink, u->rtpoll);
u->frame_size = frame_size;
- u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size);
- u->nfragments = nfrags;
- u->hwbuf_size = u->fragment_size * nfrags;
- u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec);
+ u->fragment_size = frag_size = (size_t) (period_frames * frame_size);
+ u->hwbuf_size = buffer_size = (size_t) (buffer_frames * frame_size);
pa_cvolume_mute(&u->hardware_volume, u->sink->sample_spec.channels);
- pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms",
- nfrags, (long unsigned) u->fragment_size,
+ pa_log_info("Using %0.1f fragments of size %lu bytes (%0.2fms), buffer size is %lu bytes (%0.2fms)",
+ (double) u->hwbuf_size / (double) u->fragment_size,
+ (long unsigned) u->fragment_size,
+ (double) pa_bytes_to_usec(u->fragment_size, &ss) / PA_USEC_PER_MSEC,
+ (long unsigned) u->hwbuf_size,
(double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC);
pa_sink_set_max_request(u->sink, u->hwbuf_size);
pa_sink_set_max_rewind(u->sink, u->hwbuf_size);
if (u->use_tsched) {
- u->watermark_step = pa_usec_to_bytes(TSCHED_WATERMARK_STEP_USEC, &u->sink->sample_spec);
+ u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec);
+
+ u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->sink->sample_spec);
+ u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->sink->sample_spec);
+
+ u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->sink->sample_spec);
+ u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->sink->sample_spec);
fix_min_sleep_wakeup(u);
fix_tsched_watermark(u);
@@ -1784,6 +1894,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
} else
pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->hwbuf_size, &ss));
+
reserve_update(u);
if (update_sw_params(u) < 0)
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index 9a51f857..e775b20c 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -59,11 +59,24 @@
/* #define DEBUG_TIMING */
#define DEFAULT_DEVICE "default"
-#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */
-#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */
-#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
-#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
-#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms */
+
+#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */
+#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */
+
+#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
+#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms */
+#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s */
+#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (0*PA_USEC_PER_MSEC) /* 0ms */
+#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms */
+#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
+
+#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
+#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms */
+
+#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms */
+#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms */
+
+#define VOLUME_ACCURACY (PA_VOLUME_NORM/100)
struct userdata {
pa_core *core;
@@ -91,9 +104,12 @@ struct userdata {
hwbuf_unused,
min_sleep,
min_wakeup,
- watermark_step;
+ watermark_inc_step,
+ watermark_dec_step,
+ watermark_inc_threshold,
+ watermark_dec_threshold;
- unsigned nfragments;
+ pa_usec_t watermark_dec_not_before;
char *device_name;
char *control_device;
@@ -106,6 +122,8 @@ struct userdata {
pa_smoother *smoother;
uint64_t read_count;
+ pa_usec_t smoother_interval;
+ pa_usec_t last_smoother_update;
pa_reserve_wrapper *reserve;
pa_hook_slot *reserve_slot;
@@ -234,6 +252,7 @@ static int reserve_monitor_init(struct userdata *u, const char *dname) {
static void fix_min_sleep_wakeup(struct userdata *u) {
size_t max_use, max_use_2;
pa_assert(u);
+ pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
max_use_2 = pa_frame_align(max_use/2, &u->source->sample_spec);
@@ -248,6 +267,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) {
static void fix_tsched_watermark(struct userdata *u) {
size_t max_use;
pa_assert(u);
+ pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
@@ -258,7 +278,7 @@ static void fix_tsched_watermark(struct userdata *u) {
u->tsched_watermark = u->min_wakeup;
}
-static void adjust_after_overrun(struct userdata *u) {
+static void increase_watermark(struct userdata *u) {
size_t old_watermark;
pa_usec_t old_min_latency, new_min_latency;
@@ -267,36 +287,72 @@ static void adjust_after_overrun(struct userdata *u) {
/* First, just try to increase the watermark */
old_watermark = u->tsched_watermark;
- u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_step);
-
+ u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step);
fix_tsched_watermark(u);
if (old_watermark != u->tsched_watermark) {
- pa_log_notice("Increasing wakeup watermark to %0.2f ms",
- (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC);
+ pa_log_info("Increasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC);
return;
}
/* Hmm, we cannot increase the watermark any further, hence let's raise the latency */
old_min_latency = u->source->thread_info.min_latency;
- new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_STEP_USEC);
+ new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC);
new_min_latency = PA_MIN(new_min_latency, u->source->thread_info.max_latency);
if (old_min_latency != new_min_latency) {
- pa_log_notice("Increasing minimal latency to %0.2f ms",
- (double) new_min_latency / PA_USEC_PER_MSEC);
+ pa_log_info("Increasing minimal latency to %0.2f ms",
+ (double) new_min_latency / PA_USEC_PER_MSEC);
pa_source_set_latency_range_within_thread(u->source, new_min_latency, u->source->thread_info.max_latency);
- return;
}
/* When we reach this we're officialy fucked! */
}
+static void decrease_watermark(struct userdata *u) {
+ size_t old_watermark;
+ pa_usec_t now;
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ now = pa_rtclock_now();
+
+ if (u->watermark_dec_not_before <= 0)
+ goto restart;
+
+ if (u->watermark_dec_not_before > now)
+ return;
+
+ old_watermark = u->tsched_watermark;
+
+ if (u->tsched_watermark < u->watermark_dec_step)
+ u->tsched_watermark = u->tsched_watermark / 2;
+ else
+ u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step);
+
+ fix_tsched_watermark(u);
+
+ if (old_watermark != u->tsched_watermark)
+ pa_log_info("Decreasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC);
+
+ /* We don't change the latency range*/
+
+restart:
+ u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC;
+}
+
static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
pa_usec_t wm, usec;
+ pa_assert(sleep_usec);
+ pa_assert(process_usec);
+
pa_assert(u);
+ pa_assert(u->use_tsched);
usec = pa_source_get_requested_latency_within_thread(u->source);
@@ -345,24 +401,23 @@ static int try_recover(struct userdata *u, const char *call, int err) {
return 0;
}
-static size_t check_left_to_record(struct userdata *u, size_t n_bytes) {
+static size_t check_left_to_record(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) {
size_t left_to_record;
size_t rec_space = u->hwbuf_size - u->hwbuf_unused;
+ pa_bool_t overrun = FALSE;
/* We use <= instead of < for this check here because an overrun
* only happens after the last sample was processed, not already when
* it is removed from the buffer. This is particularly important
* when block transfer is used. */
- if (n_bytes <= rec_space) {
+ if (n_bytes <= rec_space)
left_to_record = rec_space - n_bytes;
+ else {
-#ifdef DEBUG_TIMING
- pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC);
-#endif
-
- } else {
+ /* We got a dropout. What a mess! */
left_to_record = 0;
+ overrun = TRUE;
#ifdef DEBUG_TIMING
PA_DEBUG_TRAP;
@@ -370,15 +425,36 @@ static size_t check_left_to_record(struct userdata *u, size_t n_bytes) {
if (pa_log_ratelimit())
pa_log_info("Overrun!");
+ }
- if (u->use_tsched)
- adjust_after_overrun(u);
+#ifdef DEBUG_TIMING
+ pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC);
+#endif
+
+ if (u->use_tsched) {
+ pa_bool_t reset_not_before = TRUE;
+
+ if (overrun || left_to_record < u->watermark_inc_threshold)
+ increase_watermark(u);
+ else if (left_to_record > u->watermark_dec_threshold) {
+ reset_not_before = FALSE;
+
+ /* We decrease the watermark only if have actually been
+ * woken up by a timeout. If something else woke us up
+ * it's too easy to fulfill the deadlines... */
+
+ if (on_timeout)
+ decrease_watermark(u);
+ }
+
+ if (reset_not_before)
+ u->watermark_dec_not_before = 0;
}
return left_to_record;
}
-static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
+static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
pa_bool_t work_done = FALSE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_record;
@@ -410,7 +486,8 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
pa_log_debug("avail: %lu", (unsigned long) n_bytes);
#endif
- left_to_record = check_left_to_record(u, n_bytes);
+ left_to_record = check_left_to_record(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
if (u->use_tsched)
if (!polled &&
@@ -536,7 +613,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
return work_done ? 1 : 0;
}
-static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
+static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
int work_done = FALSE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_record;
@@ -563,7 +640,8 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
}
n_bytes = (size_t) n * u->frame_size;
- left_to_record = check_left_to_record(u, n_bytes);
+ left_to_record = check_left_to_record(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
if (u->use_tsched)
if (!polled &&
@@ -689,15 +767,23 @@ static void update_smoother(struct userdata *u) {
now1 = pa_timespec_load(&htstamp);
}
- position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size);
-
/* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
if (now1 <= 0)
now1 = pa_rtclock_now();
+ /* check if the time since the last update is bigger than the interval */
+ if (u->last_smoother_update > 0)
+ if (u->last_smoother_update + u->smoother_interval > now1)
+ return;
+
+ position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size);
now2 = pa_bytes_to_usec(position, &u->source->sample_spec);
pa_smoother_put(u->smoother, now1, now2);
+
+ u->last_smoother_update = now1;
+ /* exponentially increase the update interval up to the MAX limit */
+ u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL);
}
static pa_usec_t source_get_latency(struct userdata *u) {
@@ -791,7 +877,7 @@ static int update_sw_params(struct userdata *u) {
pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min);
- if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) {
+ if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min, !u->use_tsched)) < 0) {
pa_log("Failed to set software parameters: %s", pa_alsa_strerror(err));
return err;
}
@@ -803,8 +889,7 @@ static int unsuspend(struct userdata *u) {
pa_sample_spec ss;
int err;
pa_bool_t b, d;
- unsigned nfrags;
- snd_pcm_uframes_t period_size;
+ snd_pcm_uframes_t period_size, buffer_size;
pa_assert(u);
pa_assert(!u->pcm_handle);
@@ -812,7 +897,7 @@ static int unsuspend(struct userdata *u) {
pa_log_info("Trying resume...");
if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE,
- /*SND_PCM_NONBLOCK|*/
+ SND_PCM_NONBLOCK|
SND_PCM_NO_AUTO_RESAMPLE|
SND_PCM_NO_AUTO_CHANNELS|
SND_PCM_NO_AUTO_FORMAT)) < 0) {
@@ -821,12 +906,12 @@ static int unsuspend(struct userdata *u) {
}
ss = u->source->sample_spec;
- nfrags = u->nfragments;
period_size = u->fragment_size / u->frame_size;
+ buffer_size = u->hwbuf_size / u->frame_size;
b = u->use_mmap;
d = u->use_tsched;
- if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) {
+ if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &period_size, &buffer_size, 0, &b, &d, TRUE)) < 0) {
pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err));
goto fail;
}
@@ -841,10 +926,11 @@ static int unsuspend(struct userdata *u) {
goto fail;
}
- if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) {
- pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)",
- (unsigned long) u->nfragments, (unsigned long) u->fragment_size,
- (unsigned long) nfrags, period_size * u->frame_size);
+ if (period_size*u->frame_size != u->fragment_size ||
+ buffer_size*u->frame_size != u->hwbuf_size) {
+ pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)",
+ (unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size,
+ (unsigned long) (buffer_size*u->fragment_size), (unsigned long) (period_size*u->frame_size));
goto fail;
}
@@ -860,6 +946,8 @@ static int unsuspend(struct userdata *u) {
u->read_count = 0;
pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
+ u->last_smoother_update = 0;
pa_log_info("Resumed successfully...");
@@ -871,7 +959,7 @@ fail:
u->pcm_handle = NULL;
}
- return -1;
+ return -PA_ERR_IO;
}
static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
@@ -894,30 +982,34 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
- case PA_SOURCE_SUSPENDED:
+ case PA_SOURCE_SUSPENDED: {
+ int r;
pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state));
- if (suspend(u) < 0)
- return -1;
+ if ((r = suspend(u)) < 0)
+ return r;
break;
+ }
case PA_SOURCE_IDLE:
- case PA_SOURCE_RUNNING:
+ case PA_SOURCE_RUNNING: {
+ int r;
if (u->source->thread_info.state == PA_SOURCE_INIT) {
if (build_pollfd(u) < 0)
- return -1;
+ return -PA_ERR_IO;
snd_pcm_start(u->pcm_handle);
}
if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) {
- if (unsuspend(u) < 0)
- return -1;
+ if ((r = unsuspend(u)) < 0)
+ return r;
}
break;
+ }
case PA_SOURCE_UNLINKED:
case PA_SOURCE_INIT:
@@ -945,7 +1037,7 @@ static int source_set_state_cb(pa_source *s, pa_source_state_t new_state) {
reserve_done(u);
else if (old_state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(new_state))
if (reserve_init(u, u->device_name) < 0)
- return -1;
+ return -PA_ERR_BUSY;
return 0;
}
@@ -987,15 +1079,11 @@ static void source_get_volume_cb(pa_source *s) {
if (pa_cvolume_equal(&u->hardware_volume, &r))
return;
- s->virtual_volume = u->hardware_volume = r;
-
- if (u->mixer_path->has_dB) {
- pa_cvolume reset;
+ s->volume = u->hardware_volume = r;
- /* Hmm, so the hardware volume changed, let's reset our software volume */
- pa_cvolume_reset(&reset, s->sample_spec.channels);
- pa_source_set_soft_volume(s, &reset);
- }
+ /* Hmm, so the hardware volume changed, let's reset our software volume */
+ if (u->mixer_path->has_dB)
+ pa_source_set_soft_volume(s, NULL);
}
static void source_set_volume_cb(pa_source *s) {
@@ -1008,7 +1096,7 @@ static void source_set_volume_cb(pa_source *s) {
pa_assert(u->mixer_handle);
/* Shift up by the base volume */
- pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume);
+ pa_sw_cvolume_divide_scalar(&r, &s->volume, s->base_volume);
if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
return;
@@ -1019,13 +1107,26 @@ static void source_set_volume_cb(pa_source *s) {
u->hardware_volume = r;
if (u->mixer_path->has_dB) {
+ pa_cvolume new_soft_volume;
+ pa_bool_t accurate_enough;
/* Match exactly what the user requested by software */
- pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume);
+ pa_sw_cvolume_divide(&new_soft_volume, &s->volume, &u->hardware_volume);
- pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume));
+ /* If the adjustment to do in software is only minimal we
+ * can skip it. That saves us CPU at the expense of a bit of
+ * accuracy */
+ accurate_enough =
+ (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) &&
+ (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY));
+
+ pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->volume));
pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume));
- pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume));
+ pa_log_debug("Calculated software volume: %s (accurate-enough=%s)", pa_cvolume_snprint(t, sizeof(t), &new_soft_volume),
+ pa_yes_no(accurate_enough));
+
+ if (!accurate_enough)
+ s->soft_volume = new_soft_volume;
} else {
pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));
@@ -1033,7 +1134,7 @@ static void source_set_volume_cb(pa_source *s) {
/* We can't match exactly what the user requested, hence let's
* at least tell the user about it */
- s->virtual_volume = r;
+ s->volume = r;
}
}
@@ -1132,11 +1233,12 @@ static void thread_func(void *userdata) {
if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
int work_done;
pa_usec_t sleep_usec = 0;
+ pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll);
if (u->use_mmap)
- work_done = mmap_read(u, &sleep_usec, revents & POLLIN);
+ work_done = mmap_read(u, &sleep_usec, revents & POLLIN, on_timeout);
else
- work_done = unix_read(u, &sleep_usec, revents & POLLIN);
+ work_done = unix_read(u, &sleep_usec, revents & POLLIN, on_timeout);
if (work_done < 0)
goto fail;
@@ -1383,8 +1485,8 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
const char *dev_id = NULL;
pa_sample_spec ss, requested_ss;
pa_channel_map map;
- uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark;
- snd_pcm_uframes_t period_frames, tsched_frames;
+ uint32_t nfrags, frag_size, buffer_size, tsched_size, tsched_watermark;
+ snd_pcm_uframes_t period_frames, buffer_frames, tsched_frames;
size_t frame_size;
pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE;
pa_source_new_data data;
@@ -1418,8 +1520,10 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
goto fail;
}
- hwbuf_size = frag_size * nfrags;
+ buffer_size = nfrags * frag_size;
+
period_frames = frag_size/frame_size;
+ buffer_frames = buffer_size/frame_size;
tsched_frames = tsched_size/frame_size;
if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) {
@@ -1437,10 +1541,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
goto fail;
}
- if (use_tsched && !pa_rtclock_hrtimer()) {
- pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel.");
- use_tsched = FALSE;
- }
+ use_tsched = pa_alsa_may_tsched(use_tsched);
u = pa_xnew0(struct userdata, 1);
u->core = m->core;
@@ -1458,6 +1559,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
5,
pa_rtclock_now(),
FALSE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
dev_id = pa_modargs_get_value(
ma, "device_id",
@@ -1484,7 +1586,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
&u->device_name,
&ss, &map,
SND_PCM_STREAM_CAPTURE,
- &nfrags, &period_frames, tsched_frames,
+ &period_frames, &buffer_frames, tsched_frames,
&b, &d, mapping)))
goto fail;
@@ -1498,7 +1600,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
&u->device_name,
&ss, &map,
SND_PCM_STREAM_CAPTURE,
- &nfrags, &period_frames, tsched_frames,
+ &period_frames, &buffer_frames, tsched_frames,
&b, &d, profile_set, &mapping)))
goto fail;
@@ -1509,7 +1611,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
&u->device_name,
&ss, &map,
SND_PCM_STREAM_CAPTURE,
- &nfrags, &period_frames, tsched_frames,
+ &period_frames, &buffer_frames, tsched_frames,
&b, &d, FALSE)))
goto fail;
}
@@ -1535,11 +1637,6 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
u->use_tsched = use_tsched = FALSE;
}
- if (use_tsched && !pa_alsa_pcm_is_hw(u->pcm_handle)) {
- pa_log_info("Device is not a hardware device, disabling timer-based scheduling.");
- u->use_tsched = use_tsched = FALSE;
- }
-
if (u->use_mmap)
pa_log_info("Successfully enabled mmap() mode.");
@@ -1561,7 +1658,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name);
- pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags));
+ pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (buffer_frames * frame_size));
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size));
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial"));
@@ -1602,18 +1699,25 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
pa_source_set_rtpoll(u->source, u->rtpoll);
u->frame_size = frame_size;
- u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size);
- u->nfragments = nfrags;
- u->hwbuf_size = u->fragment_size * nfrags;
- u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec);
+ u->fragment_size = frag_size = (size_t) (period_frames * frame_size);
+ u->hwbuf_size = buffer_size = (size_t) (buffer_frames * frame_size);
pa_cvolume_mute(&u->hardware_volume, u->source->sample_spec.channels);
- pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms",
- nfrags, (long unsigned) u->fragment_size,
+ pa_log_info("Using %0.1f fragments of size %lu bytes (%0.2fms), buffer size is %lu bytes (%0.2fms)",
+ (double) u->hwbuf_size / (double) u->fragment_size,
+ (long unsigned) u->fragment_size,
+ (double) pa_bytes_to_usec(u->fragment_size, &ss) / PA_USEC_PER_MSEC,
+ (long unsigned) u->hwbuf_size,
(double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC);
if (u->use_tsched) {
- u->watermark_step = pa_usec_to_bytes(TSCHED_WATERMARK_STEP_USEC, &u->source->sample_spec);
+ u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec);
+
+ u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->source->sample_spec);
+ u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->source->sample_spec);
+
+ u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->source->sample_spec);
+ u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->source->sample_spec);
fix_min_sleep_wakeup(u);
fix_tsched_watermark(u);
diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c
index a47a8958..b8d13575 100644
--- a/src/modules/alsa/alsa-util.c
+++ b/src/modules/alsa/alsa-util.c
@@ -43,6 +43,7 @@
#include <pulsecore/once.h>
#include <pulsecore/thread.h>
#include <pulsecore/conf-parser.h>
+#include <pulsecore/core-rtclock.h>
#include "alsa-util.h"
#include "alsa-mixer.h"
@@ -93,6 +94,7 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s
int ret;
pa_assert(pcm_handle);
+ pa_assert(hwparams);
pa_assert(f);
if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)
@@ -148,33 +150,71 @@ try_auto:
return -1;
}
+static int set_period_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) {
+ snd_pcm_uframes_t s;
+ int d, ret;
+
+ pa_assert(pcm_handle);
+ pa_assert(hwparams);
+
+ s = size;
+ d = 0;
+ if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) {
+ s = size;
+ d = -1;
+ if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) {
+ s = size;
+ d = 1;
+ if ((ret = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d)) < 0) {
+ pa_log_info("snd_pcm_hw_params_set_period_size_near() failed: %s", pa_alsa_strerror(ret));
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int set_buffer_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) {
+ int ret;
+
+ pa_assert(pcm_handle);
+ pa_assert(hwparams);
+
+ if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &size)) < 0) {
+ pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
/* Set the hardware parameters of the given ALSA device. Returns the
- * selected fragment settings in *period and *period_size */
+ * selected fragment settings in *buffer_size and *period_size. If tsched mode can be enabled */
int pa_alsa_set_hw_params(
snd_pcm_t *pcm_handle,
pa_sample_spec *ss,
- uint32_t *periods,
snd_pcm_uframes_t *period_size,
+ snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
pa_bool_t require_exact_channel_number) {
int ret = -1;
+ snd_pcm_hw_params_t *hwparams, *hwparams_copy;
+ int dir;
snd_pcm_uframes_t _period_size = period_size ? *period_size : 0;
- unsigned int _periods = periods ? *periods : 0;
- unsigned int r = ss->rate;
- unsigned int c = ss->channels;
- pa_sample_format_t f = ss->format;
- snd_pcm_hw_params_t *hwparams;
+ snd_pcm_uframes_t _buffer_size = buffer_size ? *buffer_size : 0;
pa_bool_t _use_mmap = use_mmap && *use_mmap;
pa_bool_t _use_tsched = use_tsched && *use_tsched;
- int dir;
+ pa_sample_spec _ss = *ss;
pa_assert(pcm_handle);
pa_assert(ss);
snd_pcm_hw_params_alloca(&hwparams);
+ snd_pcm_hw_params_alloca(&hwparams_copy);
if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) {
pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret));
@@ -208,114 +248,143 @@ int pa_alsa_set_hw_params(
if (!_use_mmap)
_use_tsched = FALSE;
- if ((ret = set_format(pcm_handle, hwparams, &f)) < 0)
+ if (!pa_alsa_pcm_is_hw(pcm_handle))
+ _use_tsched = FALSE;
+
+ if ((ret = set_format(pcm_handle, hwparams, &_ss.format)) < 0)
goto finish;
- if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) {
+ if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &_ss.rate, NULL)) < 0) {
pa_log_debug("snd_pcm_hw_params_set_rate_near() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
if (require_exact_channel_number) {
- if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) {
- pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", c, pa_alsa_strerror(ret));
+ if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, _ss.channels)) < 0) {
+ pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret));
goto finish;
}
} else {
+ unsigned int c = _ss.channels;
+
if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) {
- pa_log_debug("snd_pcm_hw_params_set_channels_near(%u) failed: %s", c, pa_alsa_strerror(ret));
+ pa_log_debug("snd_pcm_hw_params_set_channels_near(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret));
goto finish;
}
- }
- if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) {
- pa_log_debug("snd_pcm_hw_params_set_periods_integer() failed: %s", pa_alsa_strerror(ret));
- goto finish;
+ _ss.channels = c;
}
- if (_period_size > 0 && tsched_size > 0 && _periods > 0) {
- snd_pcm_uframes_t buffer_size;
- unsigned int p;
+ if (_use_tsched && tsched_size > 0) {
+ _buffer_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * _ss.rate) / ss->rate);
+ _period_size = _buffer_size;
+ } else {
+ _period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * _ss.rate) / ss->rate);
+ _buffer_size = (snd_pcm_uframes_t) (((uint64_t) _buffer_size * _ss.rate) / ss->rate);
+ }
- /* Adjust the buffer sizes, if we didn't get the rate we were asking for */
- _period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * r) / ss->rate);
- tsched_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * r) / ss->rate);
+ if (_buffer_size > 0 || _period_size > 0) {
+ snd_pcm_uframes_t max_frames = 0;
- if (_use_tsched) {
- buffer_size = 0;
+ if ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0)
+ pa_log_warn("snd_pcm_hw_params_get_buffer_size_max() failed: %s", pa_alsa_strerror(ret));
+ else
+ pa_log_debug("Maximum hw buffer size is %lu ms", (long unsigned) (max_frames * PA_MSEC_PER_SEC / _ss.rate));
- if ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size)) < 0)
- pa_log_warn("snd_pcm_hw_params_get_buffer_size_max() failed: %s", pa_alsa_strerror(ret));
- else
- pa_log_debug("Maximum hw buffer size is %u ms", (unsigned) buffer_size * 1000 / r);
+ /* Some ALSA drivers really don't like if we set the buffer
+ * size first and the number of periods second. (which would
+ * make a lot more sense to me) So, try a few combinations
+ * before we give up. */
+
+ if (_buffer_size > 0 && _period_size > 0) {
+ snd_pcm_hw_params_copy(hwparams_copy, hwparams);
+
+ /* First try: set buffer size first, followed by period size */
+ if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 &&
+ set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 &&
+ snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
+ pa_log_debug("Set buffer size first, period size second.");
+ goto success;
+ }
- _period_size = tsched_size;
- _periods = 1;
+ /* Second try: set period size first, followed by buffer size */
+ if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 &&
+ set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 &&
+ snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
+ pa_log_debug("Set period size first, buffer size second.");
+ goto success;
+ }
}
- /* Some ALSA drivers really don't like if we set the buffer
- * size first and the number of periods second. (which would
- * make a lot more sense to me) So, follow this rule and
- * adjust the periods first and the buffer size second */
-
- /* First we pass 0 as direction to get exactly what we
- * asked for. That this is necessary is presumably a bug
- * in ALSA. All in all this is mostly a hint to ALSA, so
- * we don't care if this fails. */
-
- p = _periods;
- dir = 0;
- if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &p, &dir) < 0) {
- p = _periods;
- dir = 1;
- if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &p, &dir) < 0) {
- p = _periods;
- dir = -1;
- if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &p, &dir)) < 0)
- pa_log_info("snd_pcm_hw_params_set_periods_near() failed: %s", pa_alsa_strerror(ret));
+ if (_buffer_size > 0) {
+ snd_pcm_hw_params_copy(hwparams_copy, hwparams);
+
+ /* Third try: set only buffer size */
+ if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 &&
+ snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
+ pa_log_debug("Set only buffer size second.");
+ goto success;
}
}
- /* Now set the buffer size */
- buffer_size = _periods * _period_size;
- if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0)
- pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret));
+ if (_period_size > 0) {
+ snd_pcm_hw_params_copy(hwparams_copy, hwparams);
+
+ /* Fourth try: set only period size */
+ if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 &&
+ snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
+ pa_log_debug("Set only period size second.");
+ goto success;
+ }
+ }
}
- if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0)
+ pa_log_debug("Set neither period nor buffer size.");
+
+ /* Last chance, set nothing */
+ if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) {
+ pa_log_info("snd_pcm_hw_params failed: %s", pa_alsa_strerror(ret));
goto finish;
+ }
+
+success:
- if (ss->rate != r)
- pa_log_info("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, r);
+ if (ss->rate != _ss.rate)
+ pa_log_info("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, _ss.rate);
- if (ss->channels != c)
- pa_log_info("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c);
+ if (ss->channels != _ss.channels)
+ pa_log_info("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, _ss.channels);
- if (ss->format != f)
- pa_log_info("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f));
+ if (ss->format != _ss.format)
+ pa_log_info("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(_ss.format));
if ((ret = snd_pcm_prepare(pcm_handle)) < 0) {
pa_log_info("snd_pcm_prepare() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
+ if ((ret = snd_pcm_hw_params_current(pcm_handle, hwparams)) < 0) {
+ pa_log_info("snd_pcm_hw_params_current() failed: %s", pa_alsa_strerror(ret));
+ goto finish;
+ }
+
if ((ret = snd_pcm_hw_params_get_period_size(hwparams, &_period_size, &dir)) < 0 ||
- (ret = snd_pcm_hw_params_get_periods(hwparams, &_periods, &dir)) < 0) {
- pa_log_info("snd_pcm_hw_params_get_period{s|_size}() failed: %s", pa_alsa_strerror(ret));
+ (ret = snd_pcm_hw_params_get_buffer_size(hwparams, &_buffer_size)) < 0) {
+ pa_log_info("snd_pcm_hw_params_get_{period|buffer}_size() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
/* If the sample rate deviates too much, we need to resample */
- if (r < ss->rate*.95 || r > ss->rate*1.05)
- ss->rate = r;
- ss->channels = (uint8_t) c;
- ss->format = f;
+ if (_ss.rate < ss->rate*.95 || _ss.rate > ss->rate*1.05)
+ ss->rate = _ss.rate;
+ ss->channels = _ss.channels;
+ ss->format = _ss.format;
- pa_assert(_periods > 0);
pa_assert(_period_size > 0);
+ pa_assert(_buffer_size > 0);
- if (periods)
- *periods = _periods;
+ if (buffer_size)
+ *buffer_size = _buffer_size;
if (period_size)
*period_size = _period_size;
@@ -335,7 +404,7 @@ finish:
return ret;
}
-int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) {
+int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min, pa_bool_t period_event) {
snd_pcm_sw_params_t *swparams;
snd_pcm_uframes_t boundary;
int err;
@@ -349,7 +418,7 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) {
return err;
}
- if ((err = snd_pcm_sw_params_set_period_event(pcm, swparams, 0)) < 0) {
+ if ((err = snd_pcm_sw_params_set_period_event(pcm, swparams, period_event)) < 0) {
pa_log_warn("Unable to disable period event: %s\n", pa_alsa_strerror(err));
return err;
}
@@ -393,8 +462,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
- uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
+ snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
@@ -410,8 +479,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
pa_assert(dev);
pa_assert(ss);
pa_assert(map);
- pa_assert(nfrags);
- pa_assert(period_size);
pa_assert(ps);
/* First we try to find a device string with a superset of the
@@ -433,8 +500,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
ss,
map,
mode,
- nfrags,
period_size,
+ buffer_size,
tsched_size,
use_mmap,
use_tsched,
@@ -460,8 +527,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
ss,
map,
mode,
- nfrags,
period_size,
+ buffer_size,
tsched_size,
use_mmap,
use_tsched,
@@ -478,7 +545,18 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
/* OK, we didn't find any good device, so let's try the raw hw: stuff */
d = pa_sprintf_malloc("hw:%s", dev_id);
pa_log_debug("Trying %s as last resort...", d);
- pcm_handle = pa_alsa_open_by_device_string(d, dev, ss, map, mode, nfrags, period_size, tsched_size, use_mmap, use_tsched, FALSE);
+ pcm_handle = pa_alsa_open_by_device_string(
+ d,
+ dev,
+ ss,
+ map,
+ mode,
+ period_size,
+ buffer_size,
+ tsched_size,
+ use_mmap,
+ use_tsched,
+ FALSE);
pa_xfree(d);
if (pcm_handle && mapping)
@@ -493,8 +571,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping(
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
- uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
+ snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
@@ -508,8 +586,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping(
pa_assert(dev);
pa_assert(ss);
pa_assert(map);
- pa_assert(nfrags);
- pa_assert(period_size);
pa_assert(m);
try_ss.channels = m->channel_map.channels;
@@ -524,8 +600,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping(
&try_ss,
&try_map,
mode,
- nfrags,
period_size,
+ buffer_size,
tsched_size,
use_mmap,
use_tsched,
@@ -547,8 +623,8 @@ snd_pcm_t *pa_alsa_open_by_device_string(
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
- uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
+ snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
@@ -579,7 +655,15 @@ snd_pcm_t *pa_alsa_open_by_device_string(
pa_log_debug("Managed to open %s", d);
- if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, require_exact_channel_number)) < 0) {
+ if ((err = pa_alsa_set_hw_params(
+ pcm_handle,
+ ss,
+ period_size,
+ buffer_size,
+ tsched_size,
+ use_mmap,
+ use_tsched,
+ require_exact_channel_number)) < 0) {
if (!reformat) {
reformat = TRUE;
@@ -632,8 +716,8 @@ snd_pcm_t *pa_alsa_open_by_template(
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
- uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
+ snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
@@ -653,8 +737,8 @@ snd_pcm_t *pa_alsa_open_by_template(
ss,
map,
mode,
- nfrags,
period_size,
+ buffer_size,
tsched_size,
use_mmap,
use_tsched,
@@ -900,7 +984,7 @@ void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) {
snd_ctl_card_info_alloca(&info);
if ((err = snd_ctl_open(&ctl, name, 0)) < 0) {
- pa_log_warn("Error opening low-level control device '%s'", name);
+ pa_log_warn("Error opening low-level control device '%s': %s", name, snd_strerror(err));
return;
}
@@ -1225,3 +1309,26 @@ const char* pa_alsa_strerror(int errnum) {
return translated;
}
+
+pa_bool_t pa_alsa_may_tsched(pa_bool_t want) {
+
+ if (!want)
+ return FALSE;
+
+ if (!pa_rtclock_hrtimer()) {
+ /* We cannot depend on being woken up in time when the timers
+ are inaccurate, so let's fallback to classic IO based playback
+ then. */
+ pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel.");
+ return FALSE; }
+
+ if (pa_running_in_vm()) {
+ /* We cannot depend on being woken up when we ask for in a VM,
+ * so let's fallback to classic IO based playback then. */
+ pa_log_notice("Disabling timer-based scheduling because running inside a VM.");
+ return FALSE;
+ }
+
+
+ return TRUE;
+}
diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h
index 830a922e..1d1256bd 100644
--- a/src/modules/alsa/alsa-util.h
+++ b/src/modules/alsa/alsa-util.h
@@ -42,8 +42,8 @@
int pa_alsa_set_hw_params(
snd_pcm_t *pcm_handle,
pa_sample_spec *ss, /* modified at return */
- uint32_t *periods, /* modified at return */
snd_pcm_uframes_t *period_size, /* modified at return */
+ snd_pcm_uframes_t *buffer_size, /* modified at return */
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap, /* modified at return */
pa_bool_t *use_tsched, /* modified at return */
@@ -51,7 +51,8 @@ int pa_alsa_set_hw_params(
int pa_alsa_set_sw_params(
snd_pcm_t *pcm,
- snd_pcm_uframes_t avail_min);
+ snd_pcm_uframes_t avail_min,
+ pa_bool_t period_event);
/* Picks a working mapping from the profile set based on the specified ss/map */
snd_pcm_t *pa_alsa_open_by_device_id_auto(
@@ -60,8 +61,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
pa_sample_spec *ss, /* modified at return */
pa_channel_map* map, /* modified at return */
int mode,
- uint32_t *nfrags, /* modified at return */
snd_pcm_uframes_t *period_size, /* modified at return */
+ snd_pcm_uframes_t *buffer_size, /* modified at return */
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap, /* modified at return */
pa_bool_t *use_tsched, /* modified at return */
@@ -75,8 +76,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping(
pa_sample_spec *ss, /* modified at return */
pa_channel_map* map, /* modified at return */
int mode,
- uint32_t *nfrags, /* modified at return */
snd_pcm_uframes_t *period_size, /* modified at return */
+ snd_pcm_uframes_t *buffer_size, /* modified at return */
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap, /* modified at return */
pa_bool_t *use_tsched, /* modified at return */
@@ -89,8 +90,8 @@ snd_pcm_t *pa_alsa_open_by_device_string(
pa_sample_spec *ss, /* modified at return */
pa_channel_map* map, /* modified at return */
int mode,
- uint32_t *nfrags, /* modified at return */
snd_pcm_uframes_t *period_size, /* modified at return */
+ snd_pcm_uframes_t *buffer_size, /* modified at return */
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap, /* modified at return */
pa_bool_t *use_tsched, /* modified at return */
@@ -104,8 +105,8 @@ snd_pcm_t *pa_alsa_open_by_template(
pa_sample_spec *ss, /* modified at return */
pa_channel_map* map, /* modified at return */
int mode,
- uint32_t *nfrags, /* modified at return */
snd_pcm_uframes_t *period_size, /* modified at return */
+ snd_pcm_uframes_t *buffer_size, /* modified at return */
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap, /* modified at return */
pa_bool_t *use_tsched, /* modified at return */
@@ -141,4 +142,6 @@ pa_bool_t pa_alsa_pcm_is_modem(snd_pcm_t *pcm);
const char* pa_alsa_strerror(int errnum);
+pa_bool_t pa_alsa_may_tsched(pa_bool_t want);
+
#endif
diff --git a/src/modules/alsa/mixer/paths/analog-input.conf.common b/src/modules/alsa/mixer/paths/analog-input.conf.common
index 6728a6ae..951e11fa 100644
--- a/src/modules/alsa/mixer/paths/analog-input.conf.common
+++ b/src/modules/alsa/mixer/paths/analog-input.conf.common
@@ -78,6 +78,10 @@ priority = 19
name = input-microphone
priority = 19
+[Option Input Source:Internal Mic]
+name = input-microphone
+priority = 19
+
[Option Input Source:Line]
name = input-linein
priority = 18
@@ -90,7 +94,15 @@ priority = 18
name = input-linein
priority = 18
-;;; ' Capture Source'
+[Option Input Source:Docking-Station]
+name = input-docking
+priority = 17
+
+[Option Input Source:AUX IN]
+name = input
+priority = 10
+
+;;; 'Capture Source'
[Element Capture Source]
enumeration = select
@@ -236,6 +248,31 @@ name = input-docking
[Option Capture Source:Dock Mic]
name = input-docking-microphone
+;;; 'Mic Jack Mode'
+
+[Element Mic Jack Mode]
+enumeration = select
+
+[Option Mic Jack Mode:Mic In]
+name = input-microphone
+
+[Option Mic Jack Mode:Line In]
+name = input-linein
+
+;;; 'Digital Input Source'
+
+[Element Digital Input Source]
+enumeration = select
+
+[Option Digital Input Source:Analog Inputs]
+name = input
+
+[Option Digital Input Source:Digital Mic 1]
+name = input-microphone
+
+[Option Digital Input Source:Digital Mic 2]
+name = input-microphone
+
;;; Various Boosts
[Element Capture Boost]
diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf
new file mode 100644
index 00000000..f2fd31c7
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf
@@ -0,0 +1,82 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Path for mixers that have a 'Headphone2' control
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 89
+
+[Element Hardware Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master Mono]
+switch = off
+volume = off
+
+; This profile path is intended to control the second headphones, not
+; the first headphones. But it should not hurt if we leave the
+; headphone jack enabled nonetheless.
+[Element Headphone]
+switch = mute
+volume = zero
+
+[Element Headphone2]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Speaker]
+switch = off
+volume = off
+
+[Element Front]
+switch = off
+volume = off
+
+[Element Rear]
+switch = off
+volume = off
+
+[Element Surround]
+switch = off
+volume = off
+
+[Element Side]
+switch = off
+volume = off
+
+[Element Center]
+switch = off
+volume = off
+
+[Element LFE]
+switch = off
+volume = off
+
+.include analog-output.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
index 691cb3f2..2131cfe8 100644
--- a/src/modules/alsa/mixer/paths/analog-output-headphones.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
@@ -44,6 +44,13 @@ volume = merge
override-map.1 = all
override-map.2 = all-left,all-right
+; This profile path is intended to control the first headphones, not
+; the second headphones. But it should not hurt if we leave the second
+; headphone jack enabled nonetheless.
+[Element Headphone2]
+switch = mute
+volume = zero
+
[Element Speaker]
switch = off
volume = off
@@ -56,7 +63,7 @@ volume = off
switch = off
volume = off
-[Element Sourround]
+[Element Surround]
switch = off
volume = off
diff --git a/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf
index 2db976a5..0a43e271 100644
--- a/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf
@@ -41,9 +41,16 @@ volume = merge
override-map.1 = lfe
override-map.2 = lfe,lfe
+; This profile path is intended to control the speaker, not the
+; headphones. But it should not hurt if we leave the headphone jack
+; enabled nonetheless.
[Element Headphone]
-switch = off
-volume = off
+switch = mute
+volume = zero
+
+[Element Headphone2]
+switch = mute
+volume = zero
[Element Speaker]
switch = mute
@@ -59,7 +66,7 @@ volume = off
switch = off
volume = off
-[Element Sourround]
+[Element Surround]
switch = off
volume = off
diff --git a/src/modules/alsa/mixer/paths/analog-output-mono.conf b/src/modules/alsa/mixer/paths/analog-output-mono.conf
index a58cc970..542edc40 100644
--- a/src/modules/alsa/mixer/paths/analog-output-mono.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-mono.conf
@@ -38,9 +38,16 @@ volume = merge
override-map.1 = all
override-map.2 = all-left,all-right
+; This profile path is intended to control the speaker, not the
+; headphones. But it should not hurt if we leave the headphone jack
+; enabled nonetheless.
[Element Headphone]
-switch = off
-volume = off
+switch = mute
+volume = zero
+
+[Element Headphone2]
+switch = mute
+volume = zero
[Element Speaker]
switch = mute
@@ -56,7 +63,7 @@ volume = off
switch = off
volume = off
-[Element Sourround]
+[Element Surround]
switch = off
volume = off
diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
new file mode 100644
index 00000000..aea78534
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
@@ -0,0 +1,94 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Path for mixers that have a 'Speaker' control
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 100
+
+[Element Hardware Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master Mono]
+switch = off
+volume = off
+
+; This profile path is intended to control the speaker, not the
+; headphones. But it should not hurt if we leave the headphone jack
+; enabled nonetheless.
+[Element Headphone]
+switch = mute
+volume = zero
+
+[Element Headphone2]
+switch = mute
+volume = zero
+
+[Element Speaker]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Front]
+switch = mute
+volume = merge
+override-map.1 = all-front
+override-map.2 = front-left,front-right
+
+[Element Rear]
+switch = mute
+volume = merge
+override-map.1 = all-rear
+override-map.2 = rear-left,rear-right
+
+[Element Surround]
+switch = mute
+volume = merge
+override-map.1 = all-rear
+override-map.2 = rear-left,rear-right
+
+[Element Side]
+switch = mute
+volume = merge
+override-map.1 = all-side
+override-map.2 = side-left,side-right
+
+[Element Center]
+switch = mute
+volume = merge
+override-map.1 = all-center
+override-map.2 = all-center,all-center
+
+[Element LFE]
+switch = mute
+volume = merge
+override-map.1 = lfe
+override-map.2 = lfe,lfe
+
+.include analog-output.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-output.conf b/src/modules/alsa/mixer/paths/analog-output.conf
index b412a437..d7c1223b 100644
--- a/src/modules/alsa/mixer/paths/analog-output.conf
+++ b/src/modules/alsa/mixer/paths/analog-output.conf
@@ -14,12 +14,13 @@
# along with PulseAudio; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-; Intended for the 'default' output
+; Intended for the 'default' output. Note that a-o-speaker.conf has a
+; higher priority than this
;
; See analog-output.conf.common for an explanation on the directives
[General]
-priority = 100
+priority = 99
[Element Hardware Master]
switch = mute
@@ -37,15 +38,20 @@ override-map.2 = all-left,all-right
switch = off
volume = off
+; This profile path is intended to control the default output, not the
+; headphones. But it should not hurt if we leave the headphone jack
+; enabled nonetheless.
[Element Headphone]
-switch = off
-volume = off
+switch = mute
+volume = zero
+
+[Element Headphone2]
+switch = mute
+volume = zero
[Element Speaker]
switch = mute
-volume = merge
-override-map.1 = all
-override-map.2 = all-left,all-right
+volume = off
[Element Front]
switch = mute
diff --git a/src/modules/alsa/mixer/paths/analog-output.conf.common b/src/modules/alsa/mixer/paths/analog-output.conf.common
index cc1185f4..fd7f0cfb 100644
--- a/src/modules/alsa/mixer/paths/analog-output.conf.common
+++ b/src/modules/alsa/mixer/paths/analog-output.conf.common
@@ -104,8 +104,25 @@ switch = select
[Option External Amplifier:on]
name = output-amplifier-on
-priority = 0
+priority = 10
[Option External Amplifier:off]
name = output-amplifier-off
+priority = 0
+
+;;; 'Analog Output'
+
+[Element Analog Output]
+enumeration = select
+
+[Option Analog Output:Speakers]
+name = output-speaker
priority = 10
+
+[Option Analog Output:Headphones]
+name = output-headphones
+priority = 9
+
+[Option Analog Output:FP Headphones]
+name = output-headphones
+priority = 8
diff --git a/src/modules/alsa/mixer/profile-sets/default.conf b/src/modules/alsa/mixer/profile-sets/default.conf
index ac41a8d3..046938fc 100644
--- a/src/modules/alsa/mixer/profile-sets/default.conf
+++ b/src/modules/alsa/mixer/profile-sets/default.conf
@@ -62,42 +62,42 @@ auto-profiles = yes
[Mapping analog-mono]
device-strings = hw:%f
channel-map = mono
-paths-output = analog-output analog-output-headphones analog-output-mono analog-output-lfe-on-mono
+paths-output = analog-output analog-output-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono analog-output-lfe-on-mono
paths-input = analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line
priority = 1
[Mapping analog-stereo]
device-strings = front:%f hw:%f
channel-map = left,right
-paths-output = analog-output analog-output-headphones analog-output-mono analog-output-lfe-on-mono
+paths-output = analog-output analog-output-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono analog-output-lfe-on-mono
paths-input = analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line
priority = 10
[Mapping analog-surround-40]
device-strings = surround40:%f
channel-map = front-left,front-right,rear-left,rear-right
-paths-output = analog-output analog-output-lfe-on-mono
+paths-output = analog-output analog-output-speaker analog-output-lfe-on-mono
priority = 7
direction = output
[Mapping analog-surround-41]
device-strings = surround41:%f
channel-map = front-left,front-right,rear-left,rear-right,lfe
-paths-output = analog-output analog-output-lfe-on-mono
+paths-output = analog-output analog-output-speaker analog-output-lfe-on-mono
priority = 8
direction = output
[Mapping analog-surround-50]
device-strings = surround50:%f
channel-map = front-left,front-right,rear-left,rear-right,front-center
-paths-output = analog-output analog-output-lfe-on-mono
+paths-output = analog-output analog-output-speaker analog-output-lfe-on-mono
priority = 7
direction = output
[Mapping analog-surround-51]
device-strings = surround51:%f
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
-paths-output = analog-output analog-output-lfe-on-mono
+paths-output = analog-output analog-output-speaker analog-output-lfe-on-mono
priority = 8
direction = output
@@ -105,7 +105,7 @@ direction = output
device-strings = surround71:%f
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right
description = Analog Surround 7.1
-paths-output = analog-output analog-output-lfe-on-mono
+paths-output = analog-output analog-output-speaker analog-output-lfe-on-mono
priority = 7
direction = output
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index 55f6a6e2..6bea33d7 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -329,7 +329,7 @@ int pa__init(pa_module *m) {
if (!u->profile_set)
goto fail;
- pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec);
+ pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec);
pa_card_new_data_init(&data);
data.driver = __FILE__;
diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index 66e1c31e..47d62005 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -1,7 +1,7 @@
/***
This file is part of PulseAudio.
- Copyright 2008 Joao Paulo Rechi Vita
+ Copyright 2008-2009 Joao Paulo Rechi Vita
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -95,6 +95,7 @@ static pa_bluetooth_device* device_new(const char *path) {
d->audio_state = PA_BT_AUDIO_STATE_INVALID;
d->audio_sink_state = PA_BT_AUDIO_STATE_INVALID;
+ d->audio_source_state = PA_BT_AUDIO_STATE_INVALID;
d->headset_state = PA_BT_AUDIO_STATE_INVALID;
return d;
@@ -122,9 +123,10 @@ static pa_bool_t device_is_audio(pa_bluetooth_device *d) {
return
d->device_info_valid &&
- (d->audio_state != PA_BT_AUDIO_STATE_INVALID ||
- d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID ||
- d->headset_state != PA_BT_AUDIO_STATE_INVALID);
+ (d->audio_state != PA_BT_AUDIO_STATE_INVALID &&
+ (d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID ||
+ d->audio_source_state != PA_BT_AUDIO_STATE_INVALID ||
+ d->headset_state != PA_BT_AUDIO_STATE_INVALID));
}
static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) {
@@ -226,10 +228,6 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device
node = uuid_new(value);
PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
- /* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */
- pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
- send_and_add_to_pending(y, d, m, get_properties_reply);
-
/* Vudentz said the interfaces are here when the UUIDs are announced */
if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties"));
@@ -237,8 +235,15 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device
} else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink", "GetProperties"));
send_and_add_to_pending(y, d, m, get_properties_reply);
+ } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) {
+ pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource", "GetProperties"));
+ send_and_add_to_pending(y, d, m, get_properties_reply);
}
+ /* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */
+ pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
+ send_and_add_to_pending(y, d, m, get_properties_reply);
+
if (!dbus_message_iter_next(&ai))
break;
}
@@ -278,7 +283,7 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessa
dbus_message_iter_recurse(i, &variant_i);
-/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|Headset}.%s", key); */
+/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
switch (dbus_message_iter_get_arg_type(&variant_i)) {
@@ -390,6 +395,9 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
} else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) {
if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0)
goto finish;
+ } else if (dbus_message_has_interface(p->message, "org.bluez.AudioSource")) {
+ if (parse_audio_property(y, &d->audio_source_state, &dict_i) < 0)
+ goto finish;
}
}
@@ -440,8 +448,8 @@ static void found_device(pa_bluetooth_discovery *y, const char* path) {
pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
send_and_add_to_pending(y, d, m, get_properties_reply);
- /* Before we read the other properties (Audio, AudioSink, Headset) we wait
- * that the UUID is read */
+ /* Before we read the other properties (Audio, AudioSink, AudioSource,
+ * Headset) we wait that the UUID is read */
}
static void list_devices_reply(DBusPendingCall *pending, void *userdata) {
@@ -616,6 +624,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
} else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
+ dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") ||
dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
pa_bluetooth_device *d;
@@ -643,6 +652,9 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
} else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) {
if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0)
goto fail;
+ } else if (dbus_message_has_interface(m, "org.bluez.AudioSource")) {
+ if (parse_audio_property(y, &d->audio_source_state, &arg_i) < 0)
+ goto fail;
}
run_callback(y, d, FALSE);
@@ -650,6 +662,21 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ } else if (dbus_message_is_signal(m, "org.bluez.Device", "DisconnectRequested")) {
+ pa_bluetooth_device *d;
+
+ if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
+ /* Device will disconnect in 2 sec */
+ d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED;
+ d->audio_sink_state = PA_BT_AUDIO_STATE_DISCONNECTED;
+ d->audio_source_state = PA_BT_AUDIO_STATE_DISCONNECTED;
+ d->headset_state = PA_BT_AUDIO_STATE_DISCONNECTED;
+
+ run_callback(y, d, FALSE);
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
} else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
const char *name, *old_owner, *new_owner;
@@ -696,12 +723,14 @@ const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_di
while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
if (pa_streq(d->address, address))
- return d;
+ return device_is_audio(d) ? d : NULL;
return NULL;
}
const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
+ pa_bluetooth_device *d;
+
pa_assert(y);
pa_assert(PA_REFCNT_VALUE(y) > 0);
pa_assert(path);
@@ -709,7 +738,11 @@ const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_disco
if (!pa_hook_is_firing(&y->hook))
pa_bluetooth_discovery_sync(y);
- return pa_hashmap_get(y->devices, path);
+ if ((d = pa_hashmap_get(y->devices, path)))
+ if (device_is_audio(d))
+ return d;
+
+ return NULL;
}
static int setup_dbus(pa_bluetooth_discovery *y) {
@@ -758,14 +791,16 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
if (pa_dbus_add_matches(
pa_dbus_connection_get(y->connection), &err,
- "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'",
+ "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
"type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
"type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
"type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
- "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL) < 0) {
+ "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", NULL) < 0) {
pa_log("Failed to add D-Bus matches: %s", err.message);
goto fail;
}
@@ -809,15 +844,17 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
if (y->connection) {
pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
- "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'",
+ "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
"type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
"type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
"type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
"type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
- "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL);
+ "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", NULL);
dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h
index 265caf40..e2a0c3d5 100644
--- a/src/modules/bluetooth/bluetooth-util.h
+++ b/src/modules/bluetooth/bluetooth-util.h
@@ -4,7 +4,7 @@
/***
This file is part of PulseAudio.
- Copyright 2008 Joao Paulo Rechi Vita
+ Copyright 2008-2009 Joao Paulo Rechi Vita
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -53,14 +53,13 @@ struct pa_bluetooth_uuid {
PA_LLIST_FIELDS(pa_bluetooth_uuid);
};
-/* This enum is shared among Audio, Headset, and AudioSink, although not all values are acceptable in all profiles */
+/* This enum is shared among Audio, Headset, AudioSink, and AudioSource, although not all values are acceptable in all profiles */
typedef enum pa_bt_audio_state {
PA_BT_AUDIO_STATE_INVALID = -1,
PA_BT_AUDIO_STATE_DISCONNECTED,
PA_BT_AUDIO_STATE_CONNECTING,
PA_BT_AUDIO_STATE_CONNECTED,
- PA_BT_AUDIO_STATE_PLAYING,
- PA_BT_AUDIO_STATE_LAST
+ PA_BT_AUDIO_STATE_PLAYING
} pa_bt_audio_state_t;
struct pa_bluetooth_device {
@@ -85,6 +84,9 @@ struct pa_bluetooth_device {
/* AudioSink state */
pa_bt_audio_state_t audio_sink_state;
+ /* AudioSource state */
+ pa_bt_audio_state_t audio_source_state;
+
/* Headset state */
pa_bt_audio_state_t headset_state;
};
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index 0560ef32..0ba1421b 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -1,7 +1,7 @@
/***
This file is part of PulseAudio.
- Copyright 2008 Joao Paulo Rechi Vita
+ Copyright 2008-2009 Joao Paulo Rechi Vita
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -74,7 +74,8 @@ PA_MODULE_USAGE(
"profile=<a2dp|hsp> "
"rate=<sample rate> "
"channels=<number of channels> "
- "path=<device object path>");
+ "path=<device object path> "
+ "auto_connect=<automatically connect?>");
/*
#ifdef NOKIA
@@ -98,6 +99,7 @@ static const char* const valid_modargs[] = {
"rate",
"channels",
"path",
+ "auto_connect",
#ifdef NOKIA
"sco_sink",
"sco_source",
@@ -129,6 +131,7 @@ struct hsp_info {
enum profile {
PROFILE_A2DP,
+ PROFILE_A2DP_SOURCE,
PROFILE_HSP,
PROFILE_OFF
};
@@ -140,6 +143,7 @@ struct userdata {
char *address;
char *path;
pa_bluetooth_discovery *discovery;
+ pa_bool_t auto_connect;
pa_dbus_connection *connection;
@@ -178,6 +182,7 @@ struct userdata {
};
#define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC)
+#define FIXED_LATENCY_RECORD_A2DP (25*PA_USEC_PER_MSEC)
#define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC)
#define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC)
@@ -219,9 +224,7 @@ static int service_recv(struct userdata *u, bt_audio_msg_header_t *msg, size_t r
pa_assert(u);
pa_assert(u->service_fd >= 0);
pa_assert(msg);
-
- if (room <= 0)
- room = BT_SUGGESTED_BUFFER_SIZE;
+ pa_assert(room >= sizeof(*msg));
pa_log_debug("Trying to receive message from audio service...");
@@ -234,6 +237,11 @@ static int service_recv(struct userdata *u, bt_audio_msg_header_t *msg, size_t r
return -1;
}
+ if (msg->length > room) {
+ pa_log_error("Not enough room.");
+ return -1;
+ }
+
/* Secondly, read the payload */
if (msg->length > sizeof(*msg)) {
@@ -307,7 +315,7 @@ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capa
pa_log_debug("Payload size is %lu %lu", (unsigned long) bytes_left, (unsigned long) sizeof(*codec));
- if ((u->profile == PROFILE_A2DP && codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) ||
+ if (((u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) && codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) ||
(u->profile == PROFILE_HSP && codec->transport != BT_CAPABILITIES_TRANSPORT_SCO)) {
pa_log_error("Got capabilities for wrong codec.");
return -1;
@@ -344,6 +352,26 @@ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capa
return codec->seid;
memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities));
+
+ } else if (u->profile == PROFILE_A2DP_SOURCE) {
+
+ while (bytes_left > 0) {
+ if ((codec->type == BT_A2DP_SBC_SOURCE) && !codec->lock)
+ break;
+
+ bytes_left -= codec->length;
+ codec = (const codec_capabilities_t*) ((const uint8_t*) codec + codec->length);
+ }
+
+ if (bytes_left <= 0 || codec->length != sizeof(u->a2dp.sbc_capabilities))
+ return -1;
+
+ pa_assert(codec->type == BT_A2DP_SBC_SOURCE);
+
+ if (codec->configured && seid == 0)
+ return codec->seid;
+
+ memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities));
}
return 0;
@@ -368,13 +396,13 @@ static int get_caps(struct userdata *u, uint8_t seid) {
msg.getcaps_req.seid = seid;
pa_strlcpy(msg.getcaps_req.object, u->path, sizeof(msg.getcaps_req.object));
- if (u->profile == PROFILE_A2DP)
+ if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE)
msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
else {
pa_assert(u->profile == PROFILE_HSP);
msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_SCO;
}
- msg.getcaps_req.flags = BT_FLAG_AUTOCONNECT;
+ msg.getcaps_req.flags = u->auto_connect ? BT_FLAG_AUTOCONNECT : 0;
if (service_send(u, &msg.getcaps_req.h) < 0)
return -1;
@@ -451,7 +479,7 @@ static int setup_a2dp(struct userdata *u) {
};
pa_assert(u);
- pa_assert(u->profile == PROFILE_A2DP);
+ pa_assert(u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE);
cap = &u->a2dp.sbc_capabilities;
@@ -652,8 +680,8 @@ static int set_conf(struct userdata *u) {
msg.open_req.h.length = sizeof(msg.open_req);
pa_strlcpy(msg.open_req.object, u->path, sizeof(msg.open_req.object));
- msg.open_req.seid = u->profile == PROFILE_A2DP ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1;
- msg.open_req.lock = u->profile == PROFILE_A2DP ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK;
+ msg.open_req.seid = (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1;
+ msg.open_req.lock = (u->profile == PROFILE_A2DP) ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK;
if (service_send(u, &msg.open_req.h) < 0)
return -1;
@@ -661,7 +689,7 @@ static int set_conf(struct userdata *u) {
if (service_expect(u, &msg.open_rsp.h, sizeof(msg), BT_OPEN, sizeof(msg.open_rsp)) < 0)
return -1;
- if (u->profile == PROFILE_A2DP ) {
+ if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
u->sample_spec.format = PA_SAMPLE_S16LE;
if (setup_a2dp(u) < 0)
@@ -679,7 +707,7 @@ static int set_conf(struct userdata *u) {
msg.setconf_req.h.name = BT_SET_CONFIGURATION;
msg.setconf_req.h.length = sizeof(msg.setconf_req);
- if (u->profile == PROFILE_A2DP) {
+ if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities, sizeof(u->a2dp.sbc_capabilities));
} else {
msg.setconf_req.codec.transport = BT_CAPABILITIES_TRANSPORT_SCO;
@@ -697,7 +725,7 @@ static int set_conf(struct userdata *u) {
u->link_mtu = msg.setconf_rsp.link_mtu;
/* setup SBC encoder now we agree on parameters */
- if (u->profile == PROFILE_A2DP) {
+ if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
setup_sbc(&u->a2dp);
u->block_size =
@@ -881,7 +909,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
*((pa_usec_t*) data) = wi > ri ? wi - ri : 0;
}
- *((pa_usec_t*) data) += u->sink->fixed_latency;
+ *((pa_usec_t*) data) += u->sink->thread_info.fixed_latency;
return 0;
}
}
@@ -943,7 +971,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
ri = pa_bytes_to_usec(u->read_index, &u->sample_spec);
- *((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->fixed_latency;
+ *((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->thread_info.fixed_latency;
return 0;
}
@@ -1250,6 +1278,119 @@ static int a2dp_process_render(struct userdata *u) {
return ret;
}
+static int a2dp_process_push(struct userdata *u) {
+ int ret = 0;
+ pa_memchunk memchunk;
+
+ pa_assert(u);
+ pa_assert(u->profile == PROFILE_A2DP_SOURCE);
+ pa_assert(u->source);
+ pa_assert(u->read_smoother);
+
+ memchunk.memblock = pa_memblock_new(u->core->mempool, u->block_size);
+ memchunk.index = memchunk.length = 0;
+
+ for (;;) {
+ pa_bool_t found_tstamp = FALSE;
+ pa_usec_t tstamp;
+ struct a2dp_info *a2dp;
+ struct rtp_header *header;
+ struct rtp_payload *payload;
+ const void *p;
+ void *d;
+ ssize_t l;
+ size_t to_write, to_decode;
+ unsigned frame_count;
+
+ a2dp_prepare_buffer(u);
+
+ a2dp = &u->a2dp;
+ header = a2dp->buffer;
+ payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
+
+ l = pa_read(u->stream_fd, a2dp->buffer, a2dp->buffer_size, &u->stream_write_type);
+
+ if (l <= 0) {
+
+ if (l < 0 && errno == EINTR)
+ /* Retry right away if we got interrupted */
+ continue;
+
+ else if (l < 0 && errno == EAGAIN)
+ /* Hmm, apparently the socket was not readable, give up for now. */
+ break;
+
+ pa_log_error("Failed to read data from socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
+ ret = -1;
+ break;
+ }
+
+ pa_assert((size_t) l <= a2dp->buffer_size);
+
+ u->read_index += (uint64_t) l;
+
+ /* TODO: get timestamp from rtp */
+ if (!found_tstamp) {
+ /* pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); */
+ tstamp = pa_rtclock_now();
+ }
+
+ pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
+ pa_smoother_resume(u->read_smoother, tstamp, TRUE);
+
+ p = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload);
+ to_decode = l - sizeof(*header) - sizeof(*payload);
+
+ d = pa_memblock_acquire(memchunk.memblock);
+ to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock);
+
+ while (PA_LIKELY(to_decode > 0 && to_write > 0)) {
+ size_t written;
+ ssize_t decoded;
+
+ decoded = sbc_decode(&a2dp->sbc,
+ p, to_decode,
+ d, to_write,
+ &written);
+
+ if (PA_UNLIKELY(decoded <= 0)) {
+ pa_log_error("SBC decoding error (%li)", (long) decoded);
+ pa_memblock_release(memchunk.memblock);
+ pa_memblock_unref(memchunk.memblock);
+ return -1;
+ }
+
+/* pa_log_debug("SBC: decoded: %lu; written: %lu", (unsigned long) decoded, (unsigned long) written); */
+/* pa_log_debug("SBC: frame_length: %lu; codesize: %lu", (unsigned long) a2dp->frame_length, (unsigned long) a2dp->codesize); */
+
+ pa_assert_fp((size_t) decoded <= to_decode);
+ pa_assert_fp((size_t) decoded == a2dp->frame_length);
+
+ pa_assert_fp((size_t) written <= to_write);
+ pa_assert_fp((size_t) written == a2dp->codesize);
+
+ p = (const uint8_t*) p + decoded;
+ to_decode -= decoded;
+
+ d = (uint8_t*) d + written;
+ to_write -= written;
+
+ frame_count++;
+ }
+
+ pa_memblock_release(memchunk.memblock);
+
+ pa_source_post(u->source, &memchunk);
+
+ ret = 1;
+ break;
+ }
+
+ pa_memblock_unref(memchunk.memblock);
+
+ return ret;
+}
+
static void thread_func(void *userdata) {
struct userdata *u = userdata;
unsigned do_write = 0;
@@ -1262,11 +1403,11 @@ static void thread_func(void *userdata) {
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority);
+ pa_thread_mq_install(&u->thread_mq);
+
if (start_stream_fd(u) < 0)
goto fail;
- pa_thread_mq_install(&u->thread_mq);
-
for (;;) {
struct pollfd *pollfd;
int ret;
@@ -1285,7 +1426,12 @@ static void thread_func(void *userdata) {
if (pollfd && (pollfd->revents & POLLIN)) {
int n_read;
- if ((n_read = hsp_process_push(u)) < 0)
+ if (u->profile == PROFILE_HSP)
+ n_read = hsp_process_push(u);
+ else
+ n_read = a2dp_process_push(u);
+
+ if (n_read < 0)
goto fail;
/* We just read something, so we are supposed to write something, too */
@@ -1319,18 +1465,21 @@ static void thread_func(void *userdata) {
if (u->write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) {
pa_usec_t skip_usec;
uint64_t skip_bytes;
- pa_memchunk tmp;
skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC;
skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec);
- pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream",
- (unsigned long long) skip_usec,
- (unsigned long long) skip_bytes);
+ if (skip_bytes > 0) {
+ pa_memchunk tmp;
+
+ pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream",
+ (unsigned long long) skip_usec,
+ (unsigned long long) skip_bytes);
- pa_sink_render_full(u->sink, skip_bytes, &tmp);
- pa_memblock_unref(tmp.memblock);
- u->write_index += skip_bytes;
+ pa_sink_render_full(u->sink, skip_bytes, &tmp);
+ pa_memblock_unref(tmp.memblock);
+ u->write_index += skip_bytes;
+ }
}
do_write = 1;
@@ -1446,12 +1595,12 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
if (u->sink && dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged")) {
pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
- pa_sink_volume_changed(u->sink, &v, TRUE);
+ pa_sink_volume_changed(u->sink, &v);
} else if (u->source && dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) {
pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
- pa_source_volume_changed(u->source, &v, TRUE);
+ pa_source_volume_changed(u->source, &v);
}
}
}
@@ -1473,12 +1622,12 @@ static void sink_set_volume_cb(pa_sink *s) {
if (u->profile != PROFILE_HSP)
return;
- gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM;
+ gain = (pa_cvolume_max(&s->real_volume) * 15) / PA_VOLUME_NORM;
if (gain > 15)
gain = 15;
- pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
+ pa_cvolume_set(&s->real_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetSpeakerGain"));
pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID));
@@ -1497,12 +1646,12 @@ static void source_set_volume_cb(pa_source *s) {
if (u->profile != PROFILE_HSP)
return;
- gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM;
+ gain = (pa_cvolume_max(&s->volume) * 15) / PA_VOLUME_NORM;
if (gain > 15)
gain = 15;
- pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
+ pa_cvolume_set(&s->volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetMicrophoneGain"));
pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID));
@@ -1684,7 +1833,7 @@ static int add_source(struct userdata *u) {
data.driver = __FILE__;
data.module = u->module;
pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
- pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "hsp");
+ pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP_SOURCE ? "a2dp_source" : "hsp");
if (u->profile == PROFILE_HSP)
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
data.card = u->card;
@@ -1709,7 +1858,7 @@ static int add_source(struct userdata *u) {
u->source->parent.process_msg = source_process_msg;
pa_source_set_fixed_latency(u->source,
- (/* u->profile == PROFILE_A2DP ? FIXED_LATENCY_RECORD_A2DP : */ FIXED_LATENCY_RECORD_HSP) +
+ (u->profile == PROFILE_A2DP_SOURCE ? FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) +
pa_bytes_to_usec(u->block_size, &u->sample_spec));
}
@@ -1736,7 +1885,8 @@ static void shutdown_bt(struct userdata *u) {
if (u->service_fd >= 0) {
pa_close(u->service_fd);
u->service_fd = -1;
- u->service_write_type = u->service_write_type = 0;
+ u->service_write_type = 0;
+ u->service_read_type = 0;
}
if (u->write_memchunk.memblock) {
@@ -1752,7 +1902,8 @@ static int init_bt(struct userdata *u) {
shutdown_bt(u);
u->stream_write_type = 0;
- u->service_write_type = u->service_write_type = 0;
+ u->service_write_type = 0;
+ u->service_read_type = 0;
if ((u->service_fd = bt_audio_service_open()) < 0) {
pa_log_error("Couldn't connect to bluetooth audio service");
@@ -1804,7 +1955,8 @@ static int init_profile(struct userdata *u) {
if (add_sink(u) < 0)
r = -1;
- if (u->profile == PROFILE_HSP)
+ if (u->profile == PROFILE_HSP ||
+ u->profile == PROFILE_A2DP_SOURCE)
if (add_source(u) < 0)
r = -1;
@@ -2045,6 +2197,20 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
pa_hashmap_put(data.profiles, p->name, p);
}
+ if (pa_bluetooth_uuid_has(device->uuids, A2DP_SOURCE_UUID)) {
+ p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(enum profile));
+ p->priority = 10;
+ p->n_sinks = 0;
+ p->n_sources = 1;
+ p->max_sink_channels = 0;
+ p->max_source_channels = 2;
+
+ d = PA_CARD_PROFILE_DATA(p);
+ *d = PROFILE_A2DP_SOURCE;
+
+ pa_hashmap_put(data.profiles, p->name, p);
+ }
+
if (pa_bluetooth_uuid_has(device->uuids, HSP_HS_UUID) ||
pa_bluetooth_uuid_has(device->uuids, HFP_HS_UUID)) {
p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile));
@@ -2200,6 +2366,12 @@ int pa__init(pa_module* m) {
goto fail;
}
+ u->auto_connect = TRUE;
+ if (pa_modargs_get_value_boolean(ma, "auto_connect", &u->auto_connect)) {
+ pa_log("Failed to parse auto_connect= argument");
+ goto fail;
+ }
+
channels = u->sample_spec.channels;
if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 ||
channels <= 0 || channels > PA_CHANNELS_MAX) {
diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c
index 788fee00..0085fa8e 100644
--- a/src/modules/bluetooth/module-bluetooth-discover.c
+++ b/src/modules/bluetooth/module-bluetooth-discover.c
@@ -1,7 +1,7 @@
/***
This file is part of PulseAudio.
- Copyright 2008 Joao Paulo Rechi Vita
+ Copyright 2008-2009 Joao Paulo Rechi Vita
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -84,7 +84,7 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const
mi = pa_hashmap_get(u->hashmap, d->path);
if (!d->dead &&
- d->device_connected > 0 && d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED) {
+ d->device_connected > 0 && (d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED || d->audio_source_state >= PA_BT_AUDIO_STATE_CONNECTED)) {
if (!mi) {
pa_module *m = NULL;
@@ -116,6 +116,9 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const
}
#endif
+ if (d->audio_source_state >= PA_BT_AUDIO_STATE_CONNECTED)
+ args = pa_sprintf_malloc("%s profile=\"a2dp_source\" auto_connect=no", args);
+
pa_log_debug("Loading module-bluetooth-device %s", args);
m = pa_module_load(u->module->core, "module-bluetooth-device", args);
pa_xfree(args);
diff --git a/src/modules/dbus/iface-card-profile.c b/src/modules/dbus/iface-card-profile.c
new file mode 100644
index 00000000..004e2e88
--- /dev/null
+++ b/src/modules/dbus/iface-card-profile.c
@@ -0,0 +1,228 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+
+#include "iface-card-profile.h"
+
+#define OBJECT_NAME "profile"
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+struct pa_dbusiface_card_profile {
+ uint32_t index;
+ pa_card_profile *profile;
+ char *path;
+ pa_dbus_protocol *dbus_protocol;
+};
+
+enum property_handler_index {
+ PROPERTY_HANDLER_INDEX,
+ PROPERTY_HANDLER_NAME,
+ PROPERTY_HANDLER_DESCRIPTION,
+ PROPERTY_HANDLER_SINKS,
+ PROPERTY_HANDLER_SOURCES,
+ PROPERTY_HANDLER_PRIORITY,
+ PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+ [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL },
+ [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL },
+ [PROPERTY_HANDLER_DESCRIPTION] = { .property_name = "Description", .type = "s", .get_cb = handle_get_description, .set_cb = NULL },
+ [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "u", .get_cb = handle_get_sinks, .set_cb = NULL },
+ [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "u", .get_cb = handle_get_sources, .set_cb = NULL },
+ [PROPERTY_HANDLER_PRIORITY] = { .property_name = "Priority", .type = "u", .get_cb = handle_get_priority, .set_cb = NULL },
+};
+
+static pa_dbus_interface_info profile_interface_info = {
+ .name = PA_DBUSIFACE_CARD_PROFILE_INTERFACE,
+ .method_handlers = NULL,
+ .n_method_handlers = 0,
+ .property_handlers = property_handlers,
+ .n_property_handlers = PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_get_all,
+ .signals = NULL,
+ .n_signals = 0
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card_profile *p = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &p->index);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card_profile *p = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->profile->name);
+}
+
+static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card_profile *p = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->profile->description);
+}
+
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card_profile *p = userdata;
+ dbus_uint32_t sinks = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ sinks = p->profile->n_sinks;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sinks);
+}
+
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card_profile *p = userdata;
+ dbus_uint32_t sources = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ sources = p->profile->n_sources;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sources);
+}
+
+static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card_profile *p = userdata;
+ dbus_uint32_t priority = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ priority = p->profile->priority;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &priority);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card_profile *p = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ dbus_uint32_t sinks = 0;
+ dbus_uint32_t sources = 0;
+ dbus_uint32_t priority = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ sinks = p->profile->n_sinks;
+ sources = p->profile->n_sources;
+ priority = p->profile->priority;
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &p->index);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &p->profile->name);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DESCRIPTION].property_name, DBUS_TYPE_STRING, &p->profile->description);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_UINT32, &sinks);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_UINT32, &sources);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PRIORITY].property_name, DBUS_TYPE_UINT32, &priority);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+ dbus_message_unref(reply);
+}
+
+pa_dbusiface_card_profile *pa_dbusiface_card_profile_new(
+ pa_dbusiface_card *card,
+ pa_core *core,
+ pa_card_profile *profile,
+ uint32_t idx) {
+ pa_dbusiface_card_profile *p = NULL;
+
+ pa_assert(card);
+ pa_assert(core);
+ pa_assert(profile);
+
+ p = pa_xnew(pa_dbusiface_card_profile, 1);
+ p->index = idx;
+ p->profile = profile;
+ p->path = pa_sprintf_malloc("%s/%s%u", pa_dbusiface_card_get_path(card), OBJECT_NAME, idx);
+ p->dbus_protocol = pa_dbus_protocol_get(core);
+
+ pa_assert_se(pa_dbus_protocol_add_interface(p->dbus_protocol, p->path, &profile_interface_info, p) >= 0);
+
+ return p;
+}
+
+void pa_dbusiface_card_profile_free(pa_dbusiface_card_profile *p) {
+ pa_assert(p);
+
+ pa_assert_se(pa_dbus_protocol_remove_interface(p->dbus_protocol, p->path, profile_interface_info.name) >= 0);
+
+ pa_dbus_protocol_unref(p->dbus_protocol);
+
+ pa_xfree(p->path);
+ pa_xfree(p);
+}
+
+const char *pa_dbusiface_card_profile_get_path(pa_dbusiface_card_profile *p) {
+ pa_assert(p);
+
+ return p->path;
+}
+
+const char *pa_dbusiface_card_profile_get_name(pa_dbusiface_card_profile *p) {
+ pa_assert(p);
+
+ return p->profile->name;
+}
diff --git a/src/modules/dbus/iface-card-profile.h b/src/modules/dbus/iface-card-profile.h
new file mode 100644
index 00000000..a09767f8
--- /dev/null
+++ b/src/modules/dbus/iface-card-profile.h
@@ -0,0 +1,50 @@
+#ifndef foodbusifacecardprofilehfoo
+#define foodbusifacecardprofilehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ 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.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.CardProfile.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the CardProfile interface
+ * documentation.
+ */
+
+#include <pulsecore/core-scache.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-card.h"
+
+#define PA_DBUSIFACE_CARD_PROFILE_INTERFACE PA_DBUS_CORE_INTERFACE ".CardProfile"
+
+typedef struct pa_dbusiface_card_profile pa_dbusiface_card_profile;
+
+pa_dbusiface_card_profile *pa_dbusiface_card_profile_new(
+ pa_dbusiface_card *card,
+ pa_core *core,
+ pa_card_profile *profile,
+ uint32_t idx);
+void pa_dbusiface_card_profile_free(pa_dbusiface_card_profile *p);
+
+const char *pa_dbusiface_card_profile_get_path(pa_dbusiface_card_profile *p);
+const char *pa_dbusiface_card_profile_get_name(pa_dbusiface_card_profile *p);
+
+#endif
diff --git a/src/modules/dbus/iface-card.c b/src/modules/dbus/iface-card.c
new file mode 100644
index 00000000..1714df36
--- /dev/null
+++ b/src/modules/dbus/iface-card.c
@@ -0,0 +1,561 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-card-profile.h"
+
+#include "iface-card.h"
+
+#define OBJECT_NAME "card"
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+struct pa_dbusiface_card {
+ pa_dbusiface_core *core;
+
+ pa_card *card;
+ char *path;
+ pa_hashmap *profiles;
+ uint32_t next_profile_index;
+ pa_card_profile *active_profile;
+ pa_proplist *proplist;
+
+ pa_dbus_protocol *dbus_protocol;
+ pa_subscription *subscription;
+};
+
+enum property_handler_index {
+ PROPERTY_HANDLER_INDEX,
+ PROPERTY_HANDLER_NAME,
+ PROPERTY_HANDLER_DRIVER,
+ PROPERTY_HANDLER_OWNER_MODULE,
+ PROPERTY_HANDLER_SINKS,
+ PROPERTY_HANDLER_SOURCES,
+ PROPERTY_HANDLER_PROFILES,
+ PROPERTY_HANDLER_ACTIVE_PROFILE,
+ PROPERTY_HANDLER_PROPERTY_LIST,
+ PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+ [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL },
+ [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL },
+ [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL },
+ [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL },
+ [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "ao", .get_cb = handle_get_sinks, .set_cb = NULL },
+ [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "ao", .get_cb = handle_get_sources, .set_cb = NULL },
+ [PROPERTY_HANDLER_PROFILES] = { .property_name = "Profiles", .type = "ao", .get_cb = handle_get_profiles, .set_cb = NULL },
+ [PROPERTY_HANDLER_ACTIVE_PROFILE] = { .property_name = "ActiveProfile", .type = "o", .get_cb = handle_get_active_profile, .set_cb = handle_set_active_profile },
+ [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL }
+};
+
+enum method_handler_index {
+ METHOD_HANDLER_GET_PROFILE_BY_NAME,
+ METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info get_profile_by_name_args[] = { { "name", "s", "in" }, { "profile", "o", "out" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+ [METHOD_HANDLER_GET_PROFILE_BY_NAME] = {
+ .method_name = "GetProfileByName",
+ .arguments = get_profile_by_name_args,
+ .n_arguments = sizeof(get_profile_by_name_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_get_profile_by_name }
+};
+
+enum signal_index {
+ SIGNAL_ACTIVE_PROFILE_UPDATED,
+ SIGNAL_PROPERTY_LIST_UPDATED,
+ SIGNAL_MAX
+};
+
+static pa_dbus_arg_info active_profile_updated_args[] = { { "profile", "o", NULL } };
+static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+ [SIGNAL_ACTIVE_PROFILE_UPDATED] = { .name = "ActiveProfileUpdated", .arguments = active_profile_updated_args, .n_arguments = 1 },
+ [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info card_interface_info = {
+ .name = PA_DBUSIFACE_CARD_INTERFACE,
+ .method_handlers = method_handlers,
+ .n_method_handlers = METHOD_HANDLER_MAX,
+ .property_handlers = property_handlers,
+ .n_property_handlers = PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_get_all,
+ .signals = signals,
+ .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+ dbus_uint32_t idx;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ idx = c->card->index;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->card->name);
+}
+
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->card->driver);
+}
+
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+ const char *owner_module;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ if (!c->card->module) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Card %s doesn't have an owner module.", c->card->name);
+ return;
+ }
+
+ owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_sinks(pa_dbusiface_card *c, unsigned *n) {
+ const char **sinks = NULL;
+ unsigned i = 0;
+ uint32_t idx = 0;
+ pa_sink *sink = NULL;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_idxset_size(c->card->sinks);
+
+ if (*n == 0)
+ return NULL;
+
+ sinks = pa_xnew(const char *, *n);
+
+ PA_IDXSET_FOREACH(sink, c->card->sinks, idx) {
+ sinks[i] = pa_dbusiface_core_get_sink_path(c->core, sink);
+ ++i;
+ }
+
+ return sinks;
+}
+
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+ const char **sinks;
+ unsigned n_sinks;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ sinks = get_sinks(c, &n_sinks);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks);
+
+ pa_xfree(sinks);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_sources(pa_dbusiface_card *c, unsigned *n) {
+ const char **sources = NULL;
+ unsigned i = 0;
+ uint32_t idx = 0;
+ pa_source *source = NULL;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_idxset_size(c->card->sources);
+
+ if (*n == 0)
+ return NULL;
+
+ sources = pa_xnew(const char *, *n);
+
+ PA_IDXSET_FOREACH(source, c->card->sinks, idx) {
+ sources[i] = pa_dbusiface_core_get_source_path(c->core, source);
+ ++i;
+ }
+
+ return sources;
+}
+
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+ const char **sources;
+ unsigned n_sources;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ sources = get_sources(c, &n_sources);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sources, n_sources);
+
+ pa_xfree(sources);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_profiles(pa_dbusiface_card *c, unsigned *n) {
+ const char **profiles;
+ unsigned i = 0;
+ void *state = NULL;
+ pa_dbusiface_card_profile *profile;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_hashmap_size(c->profiles);
+
+ if (*n == 0)
+ return NULL;
+
+ profiles = pa_xnew(const char *, *n);
+
+ PA_HASHMAP_FOREACH(profile, c->profiles, state)
+ profiles[i++] = pa_dbusiface_card_profile_get_path(profile);
+
+ return profiles;
+}
+
+static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+ const char **profiles;
+ unsigned n_profiles;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ profiles = get_profiles(c, &n_profiles);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles);
+
+ pa_xfree(profiles);
+}
+
+static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+ const char *active_profile;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ if (!c->active_profile) {
+ pa_assert(pa_hashmap_isempty(c->profiles));
+
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "The card %s has no profiles, and therefore there's no active profile either.", c->card->name);
+ return;
+ }
+
+ active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name));
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &active_profile);
+}
+
+static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+ const char *new_active_path;
+ pa_dbusiface_card_profile *new_active;
+ int r;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(c);
+
+ if (!c->active_profile) {
+ pa_assert(pa_hashmap_isempty(c->profiles));
+
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "The card %s has no profiles, and therefore there's no active profile either.",
+ c->card->name);
+ return;
+ }
+
+ dbus_message_iter_get_basic(iter, &new_active_path);
+
+ if (!(new_active = pa_hashmap_get(c->profiles, new_active_path))) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile.", new_active_path);
+ return;
+ }
+
+ if ((r = pa_card_set_profile(c->card, pa_dbusiface_card_profile_get_name(new_active), TRUE)) < 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+ "Internal error in PulseAudio: pa_card_set_profile() failed with error code %i.", r);
+ return;
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_dbus_send_proplist_variant_reply(conn, msg, c->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ dbus_uint32_t idx;
+ const char *owner_module = NULL;
+ const char **sinks = NULL;
+ unsigned n_sinks = 0;
+ const char **sources = NULL;
+ unsigned n_sources = 0;
+ const char **profiles = NULL;
+ unsigned n_profiles = 0;
+ const char *active_profile = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ idx = c->card->index;
+ if (c->card->module)
+ owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module);
+ sinks = get_sinks(c, &n_sinks);
+ sources = get_sources(c, &n_sources);
+ profiles = get_profiles(c, &n_profiles);
+ if (c->active_profile)
+ active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name));
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &c->card->name);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->card->driver);
+
+ if (owner_module)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module);
+
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_OBJECT_PATH, sources, n_sources);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROFILES].property_name, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles);
+
+ if (active_profile)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACTIVE_PROFILE].property_name, DBUS_TYPE_OBJECT_PATH, &active_profile);
+
+ pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->proplist);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+
+ pa_xfree(sinks);
+ pa_xfree(sources);
+ pa_xfree(profiles);
+}
+
+static void handle_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+ const char *profile_name = NULL;
+ pa_dbusiface_card_profile *profile = NULL;
+ const char *profile_path = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &profile_name, DBUS_TYPE_INVALID));
+
+ if (!(profile = pa_hashmap_get(c->profiles, profile_name))) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile on card %s.", profile_name, c->card->name);
+ return;
+ }
+
+ profile_path = pa_dbusiface_card_profile_get_path(profile);
+
+ pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &profile_path);
+}
+
+static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ pa_dbusiface_card *c = userdata;
+ DBusMessage *signal = NULL;
+
+ pa_assert(core);
+ pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CARD);
+ pa_assert(c);
+
+ /* We can't use idx != c->card->index, because the c->card pointer may
+ * be stale at this point. */
+ if (pa_idxset_get_by_index(core->cards, idx) != c->card)
+ return;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+ return;
+
+ if (c->active_profile != c->card->active_profile) {
+ const char *object_path;
+
+ c->active_profile = c->card->active_profile;
+ object_path = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name));
+
+ pa_assert_se(signal = dbus_message_new_signal(c->path,
+ PA_DBUSIFACE_CARD_INTERFACE,
+ signals[SIGNAL_ACTIVE_PROFILE_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+
+ if (!pa_proplist_equal(c->proplist, c->card->proplist)) {
+ DBusMessageIter msg_iter;
+
+ pa_proplist_update(c->proplist, PA_UPDATE_SET, c->card->proplist);
+
+ pa_assert_se(signal = dbus_message_new_signal(c->path,
+ PA_DBUSIFACE_CARD_INTERFACE,
+ signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+ dbus_message_iter_init_append(signal, &msg_iter);
+ pa_dbus_append_proplist(&msg_iter, c->proplist);
+
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+}
+
+pa_dbusiface_card *pa_dbusiface_card_new(pa_dbusiface_core *core, pa_card *card) {
+ pa_dbusiface_card *c = NULL;
+
+ pa_assert(core);
+ pa_assert(card);
+
+ c = pa_xnew0(pa_dbusiface_card, 1);
+ c->core = core;
+ c->card = card;
+ c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, card->index);
+ c->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ c->next_profile_index = 0;
+ c->active_profile = NULL;
+ c->proplist = pa_proplist_copy(card->proplist);
+ c->dbus_protocol = pa_dbus_protocol_get(card->core);
+ c->subscription = pa_subscription_new(card->core, PA_SUBSCRIPTION_MASK_CARD, subscription_cb, c);
+
+ if (card->profiles) {
+ pa_card_profile *profile;
+ void *state = NULL;
+
+ PA_HASHMAP_FOREACH(profile, card->profiles, state) {
+ pa_dbusiface_card_profile *p = pa_dbusiface_card_profile_new(c, card->core, profile, c->next_profile_index++);
+ pa_hashmap_put(c->profiles, pa_dbusiface_card_profile_get_name(p), p);
+ }
+ pa_assert_se(c->active_profile = card->active_profile);
+ }
+
+ pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &card_interface_info, c) >= 0);
+
+ return c;
+}
+
+static void profile_free_cb(void *p, void *userdata) {
+ pa_dbusiface_card_profile *profile = p;
+
+ pa_assert(profile);
+
+ pa_dbusiface_card_profile_free(profile);
+}
+
+void pa_dbusiface_card_free(pa_dbusiface_card *c) {
+ pa_assert(c);
+
+ pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, card_interface_info.name) >= 0);
+
+ pa_hashmap_free(c->profiles, profile_free_cb, NULL);
+ pa_proplist_free(c->proplist);
+ pa_dbus_protocol_unref(c->dbus_protocol);
+ pa_subscription_free(c->subscription);
+
+ pa_xfree(c->path);
+ pa_xfree(c);
+}
+
+const char *pa_dbusiface_card_get_path(pa_dbusiface_card *c) {
+ pa_assert(c);
+
+ return c->path;
+}
diff --git a/src/modules/dbus/iface-card.h b/src/modules/dbus/iface-card.h
new file mode 100644
index 00000000..e2c08a3b
--- /dev/null
+++ b/src/modules/dbus/iface-card.h
@@ -0,0 +1,45 @@
+#ifndef foodbusifacecardhfoo
+#define foodbusifacecardhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ 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.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Card.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Card interface
+ * documentation.
+ */
+
+#include <pulsecore/card.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_CARD_INTERFACE PA_DBUS_CORE_INTERFACE ".Card"
+
+typedef struct pa_dbusiface_card pa_dbusiface_card;
+
+pa_dbusiface_card *pa_dbusiface_card_new(pa_dbusiface_core *core, pa_card *card);
+void pa_dbusiface_card_free(pa_dbusiface_card *c);
+
+const char *pa_dbusiface_card_get_path(pa_dbusiface_card *c);
+
+#endif
diff --git a/src/modules/dbus/iface-client.c b/src/modules/dbus/iface-client.c
new file mode 100644
index 00000000..546370f9
--- /dev/null
+++ b/src/modules/dbus/iface-client.c
@@ -0,0 +1,459 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+ Copyright 2009 Vincent Filali-Ansary <filali.v@azurdigitalnetworks.net>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-client.h"
+
+#define OBJECT_NAME "client"
+
+struct pa_dbusiface_client {
+ pa_dbusiface_core *core;
+
+ pa_client *client;
+ char *path;
+ pa_proplist *proplist;
+
+ pa_dbus_protocol *dbus_protocol;
+ pa_subscription *subscription;
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum property_handler_index {
+ PROPERTY_HANDLER_INDEX,
+ PROPERTY_HANDLER_DRIVER,
+ PROPERTY_HANDLER_OWNER_MODULE,
+ PROPERTY_HANDLER_PLAYBACK_STREAMS,
+ PROPERTY_HANDLER_RECORD_STREAMS,
+ PROPERTY_HANDLER_PROPERTY_LIST,
+ PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+ [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL },
+ [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL },
+ [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL },
+ [PROPERTY_HANDLER_PLAYBACK_STREAMS] = { .property_name = "PlaybackStreams", .type = "ao", .get_cb = handle_get_playback_streams, .set_cb = NULL },
+ [PROPERTY_HANDLER_RECORD_STREAMS] = { .property_name = "RecordStreams", .type = "ao", .get_cb = handle_get_record_streams, .set_cb = NULL },
+ [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL }
+};
+
+enum method_handler_index {
+ METHOD_HANDLER_KILL,
+ METHOD_HANDLER_UPDATE_PROPERTIES,
+ METHOD_HANDLER_REMOVE_PROPERTIES,
+ METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info update_properties_args[] = { { "property_list", "a{say}", "in" }, { "update_mode", "u", "in" } };
+static pa_dbus_arg_info remove_properties_args[] = { { "keys", "as", "in" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+ [METHOD_HANDLER_KILL] = {
+ .method_name = "Kill",
+ .arguments = NULL,
+ .n_arguments = 0,
+ .receive_cb = handle_kill },
+ [METHOD_HANDLER_UPDATE_PROPERTIES] = {
+ .method_name = "UpdateProperties",
+ .arguments = update_properties_args,
+ .n_arguments = sizeof(update_properties_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_update_properties },
+ [METHOD_HANDLER_REMOVE_PROPERTIES] = {
+ .method_name = "RemoveProperties",
+ .arguments = remove_properties_args,
+ .n_arguments = sizeof(remove_properties_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_remove_properties }
+};
+
+enum signal_index {
+ SIGNAL_PROPERTY_LIST_UPDATED,
+ SIGNAL_CLIENT_EVENT,
+ SIGNAL_MAX
+};
+
+static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
+static pa_dbus_arg_info client_event_args[] = { { "name", "s", NULL },
+ { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+ [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 },
+ /* ClientEvent is sent from module-dbus-protocol.c. */
+ [SIGNAL_CLIENT_EVENT] = { .name = "ClientEvent", .arguments = client_event_args, .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info client_interface_info = {
+ .name = PA_DBUSIFACE_CLIENT_INTERFACE,
+ .method_handlers = method_handlers,
+ .n_method_handlers = METHOD_HANDLER_MAX,
+ .property_handlers = property_handlers,
+ .n_property_handlers = PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_get_all,
+ .signals = signals,
+ .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_client *c = userdata;
+ dbus_uint32_t idx = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ idx = c->client->index;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_client *c = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->client->driver);
+}
+
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_client *c = userdata;
+ const char *owner_module = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ if (!c->client->module) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Client %d doesn't have an owner module.", c->client->index);
+ return;
+ }
+
+ owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_playback_streams(pa_dbusiface_client *c, unsigned *n) {
+ const char **playback_streams = NULL;
+ unsigned i = 0;
+ uint32_t idx = 0;
+ pa_sink_input *sink_input = NULL;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_idxset_size(c->client->sink_inputs);
+
+ if (*n == 0)
+ return NULL;
+
+ playback_streams = pa_xnew(const char *, *n);
+
+ PA_IDXSET_FOREACH(sink_input, c->client->sink_inputs, idx)
+ playback_streams[i++] = pa_dbusiface_core_get_playback_stream_path(c->core, sink_input);
+
+ return playback_streams;
+}
+
+static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_client *c = userdata;
+ const char **playback_streams = NULL;
+ unsigned n_playback_streams = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ playback_streams = get_playback_streams(c, &n_playback_streams);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams);
+
+ pa_xfree(playback_streams);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_record_streams(pa_dbusiface_client *c, unsigned *n) {
+ const char **record_streams = NULL;
+ unsigned i = 0;
+ uint32_t idx = 0;
+ pa_source_output *source_output = NULL;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_idxset_size(c->client->source_outputs);
+
+ if (*n == 0)
+ return NULL;
+
+ record_streams = pa_xnew(const char *, *n);
+
+ PA_IDXSET_FOREACH(source_output, c->client->source_outputs, idx)
+ record_streams[i++] = pa_dbusiface_core_get_record_stream_path(c->core, source_output);
+
+ return record_streams;
+}
+
+static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_client *c = userdata;
+ const char **record_streams = NULL;
+ unsigned n_record_streams = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ record_streams = get_record_streams(c, &n_record_streams);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams);
+
+ pa_xfree(record_streams);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_client *c = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_dbus_send_proplist_variant_reply(conn, msg, c->client->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_client *c = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ dbus_uint32_t idx = 0;
+ const char *owner_module = NULL;
+ const char **playback_streams = NULL;
+ unsigned n_playback_streams = 0;
+ const char **record_streams = NULL;
+ unsigned n_record_streams = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ idx = c->client->index;
+ if (c->client->module)
+ owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module);
+ playback_streams = get_playback_streams(c, &n_playback_streams);
+ record_streams = get_record_streams(c, &n_record_streams);
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->client->driver);
+
+ if (owner_module)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module);
+
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PLAYBACK_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RECORD_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams);
+ pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->client->proplist);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+
+ pa_xfree(playback_streams);
+ pa_xfree(record_streams);
+}
+
+static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_client *c = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ dbus_connection_ref(conn);
+
+ pa_client_kill(c->client);
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+ dbus_connection_unref(conn);
+}
+
+static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_client *c = userdata;
+ DBusMessageIter msg_iter;
+ pa_proplist *property_list = NULL;
+ dbus_uint32_t update_mode = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client.");
+ return;
+ }
+
+ pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+
+ if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter)))
+ return;
+
+ dbus_message_iter_get_basic(&msg_iter, &update_mode);
+
+ if (!(update_mode == PA_UPDATE_SET || update_mode == PA_UPDATE_MERGE || update_mode == PA_UPDATE_REPLACE)) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid update mode: %u", update_mode);
+ goto finish;
+ }
+
+ pa_client_update_proplist(c->client, update_mode, property_list);
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+finish:
+ if (property_list)
+ pa_proplist_free(property_list);
+}
+
+static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_client *c = userdata;
+ char **keys = NULL;
+ int n_keys = 0;
+ pa_bool_t changed = FALSE;
+ int i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client.");
+ return;
+ }
+
+ pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &keys, &n_keys, DBUS_TYPE_INVALID));
+
+ for (i = 0; i < n_keys; ++i)
+ changed |= pa_proplist_unset(c->client->proplist, keys[i]) >= 0;
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+ if (changed)
+ pa_subscription_post(c->client->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index);
+
+ dbus_free_string_array(keys);
+}
+
+static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ pa_dbusiface_client *c = userdata;
+ DBusMessage *signal = NULL;
+
+ pa_assert(core);
+ pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CLIENT);
+ pa_assert(c);
+
+ /* We can't use idx != c->client->index, because the c->client pointer may
+ * be stale at this point. */
+ if (pa_idxset_get_by_index(core->clients, idx) != c->client)
+ return;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+ return;
+
+ if (!pa_proplist_equal(c->proplist, c->client->proplist)) {
+ DBusMessageIter msg_iter;
+
+ pa_proplist_update(c->proplist, PA_UPDATE_SET, c->client->proplist);
+
+ pa_assert_se(signal = dbus_message_new_signal(c->path,
+ PA_DBUSIFACE_CLIENT_INTERFACE,
+ signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+ dbus_message_iter_init_append(signal, &msg_iter);
+ pa_dbus_append_proplist(&msg_iter, c->proplist);
+
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+}
+
+pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client) {
+ pa_dbusiface_client *c = NULL;
+
+ pa_assert(core);
+ pa_assert(client);
+
+ c = pa_xnew(pa_dbusiface_client, 1);
+ c->core = core;
+ c->client = client;
+ c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, client->index);
+ c->proplist = pa_proplist_copy(client->proplist);
+ c->dbus_protocol = pa_dbus_protocol_get(client->core);
+ c->subscription = pa_subscription_new(client->core, PA_SUBSCRIPTION_MASK_CLIENT, subscription_cb, c);
+
+ pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &client_interface_info, c) >= 0);
+
+ return c;
+}
+
+void pa_dbusiface_client_free(pa_dbusiface_client *c) {
+ pa_assert(c);
+
+ pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, client_interface_info.name) >= 0);
+
+ pa_dbus_protocol_unref(c->dbus_protocol);
+
+ pa_xfree(c->path);
+ pa_xfree(c);
+}
+
+const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c) {
+ pa_assert(c);
+
+ return c->path;
+}
diff --git a/src/modules/dbus/iface-client.h b/src/modules/dbus/iface-client.h
new file mode 100644
index 00000000..e8f151cd
--- /dev/null
+++ b/src/modules/dbus/iface-client.h
@@ -0,0 +1,45 @@
+#ifndef foodbusifaceclienthfoo
+#define foodbusifaceclienthfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ 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.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Client.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Client interface
+ * documentation.
+ */
+
+#include <pulsecore/client.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_CLIENT_INTERFACE PA_DBUS_CORE_INTERFACE ".Client"
+
+typedef struct pa_dbusiface_client pa_dbusiface_client;
+
+pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client);
+void pa_dbusiface_client_free(pa_dbusiface_client *c);
+
+const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c);
+
+#endif
diff --git a/src/modules/dbus/iface-core.c b/src/modules/dbus/iface-core.c
new file mode 100644
index 00000000..169e8e55
--- /dev/null
+++ b/src/modules/dbus/iface-core.c
@@ -0,0 +1,2197 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+
+#include <dbus/dbus.h>
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/protocol-dbus.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/strbuf.h>
+
+#include "iface-card.h"
+#include "iface-client.h"
+#include "iface-device.h"
+#include "iface-memstats.h"
+#include "iface-module.h"
+#include "iface-sample.h"
+#include "iface-stream.h"
+
+#include "iface-core.h"
+
+#define INTERFACE_REVISION 0
+
+static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_version(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_is_local(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_username(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_hostname(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_default_channels(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_default_channels(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_default_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_default_sample_format(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_default_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_default_sample_rate(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_cards(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_fallback_sink(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_fallback_sink(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_fallback_source(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_fallback_source(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_samples(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_modules(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_clients(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_my_client(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_extensions(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_card_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sink_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_source_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_upload_sample(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_load_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_exit(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_listen_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_stop_listening_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+struct pa_dbusiface_core {
+ pa_core *core;
+ pa_subscription *subscription;
+
+ pa_dbus_protocol *dbus_protocol;
+
+ pa_hashmap *cards;
+ pa_hashmap *sinks_by_index;
+ pa_hashmap *sinks_by_path;
+ pa_hashmap *sources_by_index;
+ pa_hashmap *sources_by_path;
+ pa_hashmap *playback_streams;
+ pa_hashmap *record_streams;
+ pa_hashmap *samples;
+ pa_hashmap *modules;
+ pa_hashmap *clients;
+
+ pa_sink *fallback_sink;
+ pa_source *fallback_source;
+
+ pa_hook_slot *extension_registered_slot;
+ pa_hook_slot *extension_unregistered_slot;
+
+ pa_dbusiface_memstats *memstats;
+};
+
+enum property_handler_index {
+ PROPERTY_HANDLER_INTERFACE_REVISION,
+ PROPERTY_HANDLER_NAME,
+ PROPERTY_HANDLER_VERSION,
+ PROPERTY_HANDLER_IS_LOCAL,
+ PROPERTY_HANDLER_USERNAME,
+ PROPERTY_HANDLER_HOSTNAME,
+ PROPERTY_HANDLER_DEFAULT_CHANNELS,
+ PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT,
+ PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE,
+ PROPERTY_HANDLER_CARDS,
+ PROPERTY_HANDLER_SINKS,
+ PROPERTY_HANDLER_FALLBACK_SINK,
+ PROPERTY_HANDLER_SOURCES,
+ PROPERTY_HANDLER_FALLBACK_SOURCE,
+ PROPERTY_HANDLER_PLAYBACK_STREAMS,
+ PROPERTY_HANDLER_RECORD_STREAMS,
+ PROPERTY_HANDLER_SAMPLES,
+ PROPERTY_HANDLER_MODULES,
+ PROPERTY_HANDLER_CLIENTS,
+ PROPERTY_HANDLER_MY_CLIENT,
+ PROPERTY_HANDLER_EXTENSIONS,
+ PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+ [PROPERTY_HANDLER_INTERFACE_REVISION] = { .property_name = "InterfaceRevision", .type = "u", .get_cb = handle_get_interface_revision, .set_cb = NULL },
+ [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL },
+ [PROPERTY_HANDLER_VERSION] = { .property_name = "Version", .type = "s", .get_cb = handle_get_version, .set_cb = NULL },
+ [PROPERTY_HANDLER_IS_LOCAL] = { .property_name = "IsLocal", .type = "b", .get_cb = handle_get_is_local, .set_cb = NULL },
+ [PROPERTY_HANDLER_USERNAME] = { .property_name = "Username", .type = "s", .get_cb = handle_get_username, .set_cb = NULL },
+ [PROPERTY_HANDLER_HOSTNAME] = { .property_name = "Hostname", .type = "s", .get_cb = handle_get_hostname, .set_cb = NULL },
+ [PROPERTY_HANDLER_DEFAULT_CHANNELS] = { .property_name = "DefaultChannels", .type = "au", .get_cb = handle_get_default_channels, .set_cb = handle_set_default_channels },
+ [PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT] = { .property_name = "DefaultSampleFormat", .type = "u", .get_cb = handle_get_default_sample_format, .set_cb = handle_set_default_sample_format },
+ [PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE] = { .property_name = "DefaultSampleRate", .type = "u", .get_cb = handle_get_default_sample_rate, .set_cb = handle_set_default_sample_rate },
+ [PROPERTY_HANDLER_CARDS] = { .property_name = "Cards", .type = "ao", .get_cb = handle_get_cards, .set_cb = NULL },
+ [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "ao", .get_cb = handle_get_sinks, .set_cb = NULL },
+ [PROPERTY_HANDLER_FALLBACK_SINK] = { .property_name = "FallbackSink", .type = "o", .get_cb = handle_get_fallback_sink, .set_cb = handle_set_fallback_sink },
+ [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "ao", .get_cb = handle_get_sources, .set_cb = NULL },
+ [PROPERTY_HANDLER_FALLBACK_SOURCE] = { .property_name = "FallbackSource", .type = "o", .get_cb = handle_get_fallback_source, .set_cb = handle_set_fallback_source },
+ [PROPERTY_HANDLER_PLAYBACK_STREAMS] = { .property_name = "PlaybackStreams", .type = "ao", .get_cb = handle_get_playback_streams, .set_cb = NULL },
+ [PROPERTY_HANDLER_RECORD_STREAMS] = { .property_name = "RecordStreams", .type = "ao", .get_cb = handle_get_record_streams, .set_cb = NULL },
+ [PROPERTY_HANDLER_SAMPLES] = { .property_name = "Samples", .type = "ao", .get_cb = handle_get_samples, .set_cb = NULL },
+ [PROPERTY_HANDLER_MODULES] = { .property_name = "Modules", .type = "ao", .get_cb = handle_get_modules, .set_cb = NULL },
+ [PROPERTY_HANDLER_CLIENTS] = { .property_name = "Clients", .type = "ao", .get_cb = handle_get_clients, .set_cb = NULL },
+ [PROPERTY_HANDLER_MY_CLIENT] = { .property_name = "MyClient", .type = "o", .get_cb = handle_get_my_client, .set_cb = NULL },
+ [PROPERTY_HANDLER_EXTENSIONS] = { .property_name = "Extensions", .type = "as", .get_cb = handle_get_extensions, .set_cb = NULL }
+};
+
+enum method_handler_index {
+ METHOD_HANDLER_GET_CARD_BY_NAME,
+ METHOD_HANDLER_GET_SINK_BY_NAME,
+ METHOD_HANDLER_GET_SOURCE_BY_NAME,
+ METHOD_HANDLER_GET_SAMPLE_BY_NAME,
+ METHOD_HANDLER_UPLOAD_SAMPLE,
+ METHOD_HANDLER_LOAD_MODULE,
+ METHOD_HANDLER_EXIT,
+ METHOD_HANDLER_LISTEN_FOR_SIGNAL,
+ METHOD_HANDLER_STOP_LISTENING_FOR_SIGNAL,
+ METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info get_card_by_name_args[] = { { "name", "s", "in" }, { "card", "o", "out" } };
+static pa_dbus_arg_info get_sink_by_name_args[] = { { "name", "s", "in" }, { "sink", "o", "out" } };
+static pa_dbus_arg_info get_source_by_name_args[] = { { "name", "s", "in" }, { "source", "o", "out" } };
+static pa_dbus_arg_info get_sample_by_name_args[] = { { "name", "s", "in" }, { "sample", "o", "out" } };
+static pa_dbus_arg_info upload_sample_args[] = { { "name", "s", "in" },
+ { "sample_format", "u", "in" },
+ { "sample_rate", "u", "in" },
+ { "channels", "au", "in" },
+ { "default_volume", "au", "in" },
+ { "property_list", "a{say}", "in" },
+ { "data", "ay", "in" },
+ { "sample", "o", "out" } };
+static pa_dbus_arg_info load_module_args[] = { { "name", "s", "in" }, { "arguments", "a{ss}", "in" }, { "module", "o", "out" } };
+static pa_dbus_arg_info listen_for_signal_args[] = { { "signal", "s", "in" }, { "objects", "ao", "in" } };
+static pa_dbus_arg_info stop_listening_for_signal_args[] = { { "signal", "s", "in" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+ [METHOD_HANDLER_GET_CARD_BY_NAME] = {
+ .method_name = "GetCardByName",
+ .arguments = get_card_by_name_args,
+ .n_arguments = sizeof(get_card_by_name_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_get_card_by_name },
+ [METHOD_HANDLER_GET_SINK_BY_NAME] = {
+ .method_name = "GetSinkByName",
+ .arguments = get_sink_by_name_args,
+ .n_arguments = sizeof(get_sink_by_name_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_get_sink_by_name },
+ [METHOD_HANDLER_GET_SOURCE_BY_NAME] = {
+ .method_name = "GetSourceByName",
+ .arguments = get_source_by_name_args,
+ .n_arguments = sizeof(get_source_by_name_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_get_source_by_name },
+ [METHOD_HANDLER_GET_SAMPLE_BY_NAME] = {
+ .method_name = "GetSampleByName",
+ .arguments = get_sample_by_name_args,
+ .n_arguments = sizeof(get_sample_by_name_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_get_sample_by_name },
+ [METHOD_HANDLER_UPLOAD_SAMPLE] = {
+ .method_name = "UploadSample",
+ .arguments = upload_sample_args,
+ .n_arguments = sizeof(upload_sample_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_upload_sample },
+ [METHOD_HANDLER_LOAD_MODULE] = {
+ .method_name = "LoadModule",
+ .arguments = load_module_args,
+ .n_arguments = sizeof(load_module_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_load_module },
+ [METHOD_HANDLER_EXIT] = {
+ .method_name = "Exit",
+ .arguments = NULL,
+ .n_arguments = 0,
+ .receive_cb = handle_exit },
+ [METHOD_HANDLER_LISTEN_FOR_SIGNAL] = {
+ .method_name = "ListenForSignal",
+ .arguments = listen_for_signal_args,
+ .n_arguments = sizeof(listen_for_signal_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_listen_for_signal },
+ [METHOD_HANDLER_STOP_LISTENING_FOR_SIGNAL] = {
+ .method_name = "StopListeningForSignal",
+ .arguments = stop_listening_for_signal_args,
+ .n_arguments = sizeof(stop_listening_for_signal_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_stop_listening_for_signal }
+};
+
+enum signal_index {
+ SIGNAL_NEW_CARD,
+ SIGNAL_CARD_REMOVED,
+ SIGNAL_NEW_SINK,
+ SIGNAL_SINK_REMOVED,
+ SIGNAL_FALLBACK_SINK_UPDATED,
+ SIGNAL_FALLBACK_SINK_UNSET,
+ SIGNAL_NEW_SOURCE,
+ SIGNAL_SOURCE_REMOVED,
+ SIGNAL_FALLBACK_SOURCE_UPDATED,
+ SIGNAL_FALLBACK_SOURCE_UNSET,
+ SIGNAL_NEW_PLAYBACK_STREAM,
+ SIGNAL_PLAYBACK_STREAM_REMOVED,
+ SIGNAL_NEW_RECORD_STREAM,
+ SIGNAL_RECORD_STREAM_REMOVED,
+ SIGNAL_NEW_SAMPLE,
+ SIGNAL_SAMPLE_REMOVED,
+ SIGNAL_NEW_MODULE,
+ SIGNAL_MODULE_REMOVED,
+ SIGNAL_NEW_CLIENT,
+ SIGNAL_CLIENT_REMOVED,
+ SIGNAL_NEW_EXTENSION,
+ SIGNAL_EXTENSION_REMOVED,
+ SIGNAL_MAX
+};
+
+static pa_dbus_arg_info new_card_args[] = { { "card", "o", NULL } };
+static pa_dbus_arg_info card_removed_args[] = { { "card", "o", NULL } };
+static pa_dbus_arg_info new_sink_args[] = { { "sink", "o", NULL } };
+static pa_dbus_arg_info sink_removed_args[] = { { "sink", "o", NULL } };
+static pa_dbus_arg_info fallback_sink_updated_args[] = { { "sink", "o", NULL } };
+static pa_dbus_arg_info new_source_args[] = { { "source", "o", NULL } };
+static pa_dbus_arg_info source_removed_args[] = { { "source", "o", NULL } };
+static pa_dbus_arg_info fallback_source_updated_args[] = { { "source", "o", NULL } };
+static pa_dbus_arg_info new_playback_stream_args[] = { { "playback_stream", "o", NULL } };
+static pa_dbus_arg_info playback_stream_removed_args[] = { { "playback_stream", "o", NULL } };
+static pa_dbus_arg_info new_record_stream_args[] = { { "record_stream", "o", NULL } };
+static pa_dbus_arg_info record_stream_removed_args[] = { { "record_stream", "o", NULL } };
+static pa_dbus_arg_info new_sample_args[] = { { "sample", "o", NULL } };
+static pa_dbus_arg_info sample_removed_args[] = { { "sample", "o", NULL } };
+static pa_dbus_arg_info new_module_args[] = { { "module", "o", NULL } };
+static pa_dbus_arg_info module_removed_args[] = { { "module", "o", NULL } };
+static pa_dbus_arg_info new_client_args[] = { { "client", "o", NULL } };
+static pa_dbus_arg_info client_removed_args[] = { { "client", "o", NULL } };
+static pa_dbus_arg_info new_extension_args[] = { { "extension", "s", NULL } };
+static pa_dbus_arg_info extension_removed_args[] = { { "extension", "s", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+ [SIGNAL_NEW_CARD] = { .name = "NewCard", .arguments = new_card_args, .n_arguments = 1 },
+ [SIGNAL_CARD_REMOVED] = { .name = "CardRemoved", .arguments = card_removed_args, .n_arguments = 1 },
+ [SIGNAL_NEW_SINK] = { .name = "NewSink", .arguments = new_sink_args, .n_arguments = 1 },
+ [SIGNAL_SINK_REMOVED] = { .name = "SinkRemoved", .arguments = sink_removed_args, .n_arguments = 1 },
+ [SIGNAL_FALLBACK_SINK_UPDATED] = { .name = "FallbackSinkUpdated", .arguments = fallback_sink_updated_args, .n_arguments = 1 },
+ [SIGNAL_FALLBACK_SINK_UNSET] = { .name = "FallbackSinkUnset", .arguments = NULL, .n_arguments = 0 },
+ [SIGNAL_NEW_SOURCE] = { .name = "NewSource", .arguments = new_source_args, .n_arguments = 1 },
+ [SIGNAL_SOURCE_REMOVED] = { .name = "SourceRemoved", .arguments = source_removed_args, .n_arguments = 1 },
+ [SIGNAL_FALLBACK_SOURCE_UPDATED] = { .name = "FallbackSourceUpdated", .arguments = fallback_source_updated_args, .n_arguments = 1 },
+ [SIGNAL_FALLBACK_SOURCE_UNSET] = { .name = "FallbackSourceUnset", .arguments = NULL, .n_arguments = 0 },
+ [SIGNAL_NEW_PLAYBACK_STREAM] = { .name = "NewPlaybackStream", .arguments = new_playback_stream_args, .n_arguments = 1 },
+ [SIGNAL_PLAYBACK_STREAM_REMOVED] = { .name = "PlaybackStreamRemoved", .arguments = playback_stream_removed_args, .n_arguments = 1 },
+ [SIGNAL_NEW_RECORD_STREAM] = { .name = "NewRecordStream", .arguments = new_record_stream_args, .n_arguments = 1 },
+ [SIGNAL_RECORD_STREAM_REMOVED] = { .name = "RecordStreamRemoved", .arguments = record_stream_removed_args, .n_arguments = 1 },
+ [SIGNAL_NEW_SAMPLE] = { .name = "NewSample", .arguments = new_sample_args, .n_arguments = 1 },
+ [SIGNAL_SAMPLE_REMOVED] = { .name = "SampleRemoved", .arguments = sample_removed_args, .n_arguments = 1 },
+ [SIGNAL_NEW_MODULE] = { .name = "NewModule", .arguments = new_module_args, .n_arguments = 1 },
+ [SIGNAL_MODULE_REMOVED] = { .name = "ModuleRemoved", .arguments = module_removed_args, .n_arguments = 1 },
+ [SIGNAL_NEW_CLIENT] = { .name = "NewClient", .arguments = new_client_args, .n_arguments = 1 },
+ [SIGNAL_CLIENT_REMOVED] = { .name = "ClientRemoved", .arguments = client_removed_args, .n_arguments = 1 },
+ [SIGNAL_NEW_EXTENSION] = { .name = "NewExtension", .arguments = new_extension_args, .n_arguments = 1 },
+ [SIGNAL_EXTENSION_REMOVED] = { .name = "ExtensionRemoved", .arguments = extension_removed_args, .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info core_interface_info = {
+ .name = PA_DBUS_CORE_INTERFACE,
+ .method_handlers = method_handlers,
+ .n_method_handlers = METHOD_HANDLER_MAX,
+ .property_handlers = property_handlers,
+ .n_property_handlers = PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_get_all,
+ .signals = signals,
+ .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ dbus_uint32_t interface_revision = INTERFACE_REVISION;
+
+ pa_assert(conn);
+ pa_assert(msg);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &interface_revision);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ const char *server_name = PACKAGE_NAME;
+
+ pa_assert(conn);
+ pa_assert(msg);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &server_name);
+}
+
+static void handle_get_version(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ const char *version = PACKAGE_VERSION;
+
+ pa_assert(conn);
+ pa_assert(msg);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &version);
+}
+
+static dbus_bool_t get_is_local(DBusConnection *conn) {
+ int conn_fd;
+
+ pa_assert(conn);
+
+ if (!dbus_connection_get_socket(conn, &conn_fd))
+ return FALSE;
+
+ return pa_socket_is_local(conn_fd);
+}
+
+static void handle_get_is_local(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ dbus_bool_t is_local;
+
+ pa_assert(conn);
+ pa_assert(msg);
+
+ is_local = get_is_local(conn);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_local);
+}
+
+static void handle_get_username(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ char *username = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+
+ username = pa_get_user_name_malloc();
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &username);
+
+ pa_xfree(username);
+}
+
+static void handle_get_hostname(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ char *hostname = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+
+ hostname = pa_get_host_name_malloc();
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &hostname);
+
+ pa_xfree(hostname);
+}
+
+/* Caller frees the returned array. */
+static dbus_uint32_t *get_default_channels(pa_dbusiface_core *c, unsigned *n) {
+ dbus_uint32_t *default_channels = NULL;
+ unsigned i;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = c->core->default_channel_map.channels;
+ default_channels = pa_xnew(dbus_uint32_t, *n);
+
+ for (i = 0; i < *n; ++i)
+ default_channels[i] = c->core->default_channel_map.map[i];
+
+ return default_channels;
+}
+
+static void handle_get_default_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ dbus_uint32_t *default_channels = NULL;
+ unsigned n;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ default_channels = get_default_channels(c, &n);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, default_channels, n);
+
+ pa_xfree(default_channels);
+}
+
+static void handle_set_default_channels(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ DBusMessageIter array_iter;
+ pa_channel_map new_channel_map;
+ const dbus_uint32_t *default_channels;
+ int n_channels;
+ unsigned i;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(c);
+
+ pa_channel_map_init(&new_channel_map);
+
+ dbus_message_iter_recurse(iter, &array_iter);
+ dbus_message_iter_get_fixed_array(&array_iter, &default_channels, &n_channels);
+
+ if (n_channels <= 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty channel array.");
+ return;
+ }
+
+ if (n_channels > (int) PA_CHANNELS_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+ "Too many channels: %i. The maximum number of channels is %u.", n_channels, PA_CHANNELS_MAX);
+ return;
+ }
+
+ new_channel_map.channels = n_channels;
+
+ for (i = 0; i < new_channel_map.channels; ++i) {
+ if (default_channels[i] >= PA_CHANNEL_POSITION_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position: %u.", default_channels[i]);
+ return;
+ }
+
+ new_channel_map.map[i] = default_channels[i];
+ }
+
+ c->core->default_channel_map = new_channel_map;
+ c->core->default_sample_spec.channels = n_channels;
+
+ pa_dbus_send_empty_reply(conn, msg);
+};
+
+static void handle_get_default_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ dbus_uint32_t default_sample_format;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ default_sample_format = c->core->default_sample_spec.format;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &default_sample_format);
+}
+
+static void handle_set_default_sample_format(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ dbus_uint32_t default_sample_format;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(c);
+
+ dbus_message_iter_get_basic(iter, &default_sample_format);
+
+ if (default_sample_format >= PA_SAMPLE_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample format.");
+ return;
+ }
+
+ c->core->default_sample_spec.format = default_sample_format;
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_default_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ dbus_uint32_t default_sample_rate;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ default_sample_rate = c->core->default_sample_spec.rate;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &default_sample_rate);
+}
+
+static void handle_set_default_sample_rate(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ dbus_uint32_t default_sample_rate;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(c);
+
+ dbus_message_iter_get_basic(iter, &default_sample_rate);
+
+ if (default_sample_rate <= 0 || default_sample_rate > PA_RATE_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample rate.");
+ return;
+ }
+
+ c->core->default_sample_spec.rate = default_sample_rate;
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_cards(pa_dbusiface_core *c, unsigned *n) {
+ const char **cards;
+ unsigned i = 0;
+ void *state = NULL;
+ pa_dbusiface_card *card;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_hashmap_size(c->cards);
+
+ if (*n == 0)
+ return NULL;
+
+ cards = pa_xnew(const char *, *n);
+
+ PA_HASHMAP_FOREACH(card, c->cards, state)
+ cards[i++] = pa_dbusiface_card_get_path(card);
+
+ return cards;
+}
+
+static void handle_get_cards(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char **cards;
+ unsigned n;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ cards = get_cards(c, &n);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, cards, n);
+
+ pa_xfree(cards);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_sinks(pa_dbusiface_core *c, unsigned *n) {
+ const char **sinks;
+ unsigned i = 0;
+ void *state = NULL;
+ pa_dbusiface_device *sink;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_hashmap_size(c->sinks_by_index);
+
+ if (*n == 0)
+ return NULL;
+
+ sinks = pa_xnew(const char *, *n);
+
+ PA_HASHMAP_FOREACH(sink, c->sinks_by_index, state)
+ sinks[i++] = pa_dbusiface_device_get_path(sink);
+
+ return sinks;
+}
+
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char **sinks;
+ unsigned n;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ sinks = get_sinks(c, &n);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sinks, n);
+
+ pa_xfree(sinks);
+}
+
+static void handle_get_fallback_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ pa_dbusiface_device *fallback_sink;
+ const char *object_path;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ if (!c->fallback_sink) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "There are no sinks, and therefore no fallback sink either.");
+ return;
+ }
+
+ pa_assert_se((fallback_sink = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index))));
+ object_path = pa_dbusiface_device_get_path(fallback_sink);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_set_fallback_sink(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ pa_dbusiface_device *fallback_sink;
+ const char *object_path;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(c);
+
+ if (!c->fallback_sink) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "There are no sinks, and therefore no fallback sink either.");
+ return;
+ }
+
+ dbus_message_iter_get_basic(iter, &object_path);
+
+ if (!(fallback_sink = pa_hashmap_get(c->sinks_by_path, object_path))) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", object_path);
+ return;
+ }
+
+ pa_namereg_set_default_sink(c->core, pa_dbusiface_device_get_sink(fallback_sink));
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_sources(pa_dbusiface_core *c, unsigned *n) {
+ const char **sources;
+ unsigned i = 0;
+ void *state = NULL;
+ pa_dbusiface_device *source;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_hashmap_size(c->sources_by_index);
+
+ if (*n == 0)
+ return NULL;
+
+ sources = pa_xnew(const char *, *n);
+
+ PA_HASHMAP_FOREACH(source, c->sources_by_index, state)
+ sources[i++] = pa_dbusiface_device_get_path(source);
+
+ return sources;
+}
+
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char **sources;
+ unsigned n;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ sources = get_sources(c, &n);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sources, n);
+
+ pa_xfree(sources);
+}
+
+static void handle_get_fallback_source(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ pa_dbusiface_device *fallback_source;
+ const char *object_path;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ if (!c->fallback_source) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "There are no sources, and therefore no fallback source either.");
+ return;
+ }
+
+ pa_assert_se((fallback_source = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(c->fallback_source->index))));
+ object_path = pa_dbusiface_device_get_path(fallback_source);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_set_fallback_source(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ pa_dbusiface_device *fallback_source;
+ const char *object_path;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(c);
+
+ if (!c->fallback_source) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "There are no sources, and therefore no fallback source either.");
+ return;
+ }
+
+ dbus_message_iter_get_basic(iter, &object_path);
+
+ if (!(fallback_source = pa_hashmap_get(c->sources_by_path, object_path))) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", object_path);
+ return;
+ }
+
+ pa_namereg_set_default_source(c->core, pa_dbusiface_device_get_source(fallback_source));
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_playback_streams(pa_dbusiface_core *c, unsigned *n) {
+ const char **streams;
+ unsigned i = 0;
+ void *state = NULL;
+ pa_dbusiface_stream *stream;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_hashmap_size(c->playback_streams);
+
+ if (*n == 0)
+ return NULL;
+
+ streams = pa_xnew(const char *, *n);
+
+ PA_HASHMAP_FOREACH(stream, c->playback_streams, state)
+ streams[i++] = pa_dbusiface_stream_get_path(stream);
+
+ return streams;
+}
+
+static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char **playback_streams;
+ unsigned n;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ playback_streams = get_playback_streams(c, &n);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n);
+
+ pa_xfree(playback_streams);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_record_streams(pa_dbusiface_core *c, unsigned *n) {
+ const char **streams;
+ unsigned i = 0;
+ void *state = NULL;
+ pa_dbusiface_stream *stream;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_hashmap_size(c->record_streams);
+
+ if (*n == 0)
+ return NULL;
+
+ streams = pa_xnew(const char *, *n);
+
+ PA_HASHMAP_FOREACH(stream, c->record_streams, state)
+ streams[i++] = pa_dbusiface_stream_get_path(stream);
+
+ return streams;
+}
+
+static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char **record_streams;
+ unsigned n;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ record_streams = get_record_streams(c, &n);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n);
+
+ pa_xfree(record_streams);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_samples(pa_dbusiface_core *c, unsigned *n) {
+ const char **samples;
+ unsigned i = 0;
+ void *state = NULL;
+ pa_dbusiface_sample *sample;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_hashmap_size(c->samples);
+
+ if (*n == 0)
+ return NULL;
+
+ samples = pa_xnew(const char *, *n);
+
+ PA_HASHMAP_FOREACH(sample, c->samples, state)
+ samples[i++] = pa_dbusiface_sample_get_path(sample);
+
+ return samples;
+}
+
+static void handle_get_samples(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char **samples;
+ unsigned n;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ samples = get_samples(c, &n);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, samples, n);
+
+ pa_xfree(samples);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_modules(pa_dbusiface_core *c, unsigned *n) {
+ const char **modules;
+ unsigned i = 0;
+ void *state = NULL;
+ pa_dbusiface_module *module;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_hashmap_size(c->modules);
+
+ if (*n == 0)
+ return NULL;
+
+ modules = pa_xnew(const char *, *n);
+
+ PA_HASHMAP_FOREACH(module, c->modules, state)
+ modules[i++] = pa_dbusiface_module_get_path(module);
+
+ return modules;
+}
+
+static void handle_get_modules(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char **modules;
+ unsigned n;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ modules = get_modules(c, &n);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, modules, n);
+
+ pa_xfree(modules);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_clients(pa_dbusiface_core *c, unsigned *n) {
+ const char **clients;
+ unsigned i = 0;
+ void *state = NULL;
+ pa_dbusiface_client *client;
+
+ pa_assert(c);
+ pa_assert(n);
+
+ *n = pa_hashmap_size(c->clients);
+
+ if (*n == 0)
+ return NULL;
+
+ clients = pa_xnew(const char *, *n);
+
+ PA_HASHMAP_FOREACH(client, c->clients, state)
+ clients[i++] = pa_dbusiface_client_get_path(client);
+
+ return clients;
+}
+
+static void handle_get_clients(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char **clients;
+ unsigned n;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ clients = get_clients(c, &n);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, clients, n);
+
+ pa_xfree(clients);
+}
+
+static const char *get_my_client(pa_dbusiface_core *c, DBusConnection *conn) {
+ pa_client *my_client;
+
+ pa_assert(c);
+ pa_assert(conn);
+
+ pa_assert_se((my_client = pa_dbus_protocol_get_client(c->dbus_protocol, conn)));
+
+ return pa_dbusiface_client_get_path(pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(my_client->index)));
+}
+
+static void handle_get_my_client(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char *my_client;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ my_client = get_my_client(c, conn);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &my_client);
+}
+
+static void handle_get_extensions(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char **extensions;
+ unsigned n;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ extensions = pa_dbus_protocol_get_extensions(c->dbus_protocol, &n);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_STRING, extensions, n);
+
+ pa_xfree(extensions);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ dbus_uint32_t interface_revision;
+ const char *server_name;
+ const char *version;
+ dbus_bool_t is_local;
+ char *username;
+ char *hostname;
+ dbus_uint32_t *default_channels;
+ unsigned n_default_channels;
+ dbus_uint32_t default_sample_format;
+ dbus_uint32_t default_sample_rate;
+ const char **cards;
+ unsigned n_cards;
+ const char **sinks;
+ unsigned n_sinks;
+ const char *fallback_sink;
+ const char **sources;
+ unsigned n_sources;
+ const char *fallback_source;
+ const char **playback_streams;
+ unsigned n_playback_streams;
+ const char **record_streams;
+ unsigned n_record_streams;
+ const char **samples;
+ unsigned n_samples;
+ const char **modules;
+ unsigned n_modules;
+ const char **clients;
+ unsigned n_clients;
+ const char *my_client;
+ const char **extensions;
+ unsigned n_extensions;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ interface_revision = INTERFACE_REVISION;
+ server_name = PACKAGE_NAME;
+ version = PACKAGE_VERSION;
+ is_local = get_is_local(conn);
+ username = pa_get_user_name_malloc();
+ hostname = pa_get_host_name_malloc();
+ default_channels = get_default_channels(c, &n_default_channels);
+ default_sample_format = c->core->default_sample_spec.format;
+ default_sample_rate = c->core->default_sample_spec.rate;
+ cards = get_cards(c, &n_cards);
+ sinks = get_sinks(c, &n_sinks);
+ fallback_sink = c->fallback_sink
+ ? pa_dbusiface_device_get_path(pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index)))
+ : NULL;
+ sources = get_sources(c, &n_sources);
+ fallback_source = c->fallback_source
+ ? pa_dbusiface_device_get_path(pa_hashmap_get(c->sources_by_index,
+ PA_UINT32_TO_PTR(c->fallback_source->index)))
+ : NULL;
+ playback_streams = get_playback_streams(c, &n_playback_streams);
+ record_streams = get_record_streams(c, &n_record_streams);
+ samples = get_samples(c, &n_samples);
+ modules = get_modules(c, &n_modules);
+ clients = get_clients(c, &n_clients);
+ my_client = get_my_client(c, conn);
+ extensions = pa_dbus_protocol_get_extensions(c->dbus_protocol, &n_extensions);
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INTERFACE_REVISION].property_name, DBUS_TYPE_UINT32, &interface_revision);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &server_name);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VERSION].property_name, DBUS_TYPE_STRING, &version);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_LOCAL].property_name, DBUS_TYPE_BOOLEAN, &is_local);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_USERNAME].property_name, DBUS_TYPE_STRING, &username);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HOSTNAME].property_name, DBUS_TYPE_STRING, &hostname);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_CHANNELS].property_name, DBUS_TYPE_UINT32, default_channels, n_default_channels);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &default_sample_format);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &default_sample_rate);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CARDS].property_name, DBUS_TYPE_OBJECT_PATH, cards, n_cards);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks);
+
+ if (fallback_sink)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_FALLBACK_SINK].property_name, DBUS_TYPE_OBJECT_PATH, &fallback_sink);
+
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_OBJECT_PATH, sources, n_sources);
+
+ if (fallback_source)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_FALLBACK_SOURCE].property_name, DBUS_TYPE_OBJECT_PATH, &fallback_source);
+
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PLAYBACK_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RECORD_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLES].property_name, DBUS_TYPE_OBJECT_PATH, samples, n_samples);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MODULES].property_name, DBUS_TYPE_OBJECT_PATH, modules, n_modules);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CLIENTS].property_name, DBUS_TYPE_OBJECT_PATH, clients, n_clients);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MY_CLIENT].property_name, DBUS_TYPE_OBJECT_PATH, &my_client);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_EXTENSIONS].property_name, DBUS_TYPE_STRING, extensions, n_extensions);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+
+ pa_xfree(username);
+ pa_xfree(hostname);
+ pa_xfree(default_channels);
+ pa_xfree(cards);
+ pa_xfree(sinks);
+ pa_xfree(sources);
+ pa_xfree(playback_streams);
+ pa_xfree(record_streams);
+ pa_xfree(samples);
+ pa_xfree(modules);
+ pa_xfree(clients);
+ pa_xfree(extensions);
+}
+
+static void handle_get_card_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ char *card_name;
+ pa_card *card;
+ pa_dbusiface_card *dbus_card;
+ const char *object_path;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &card_name, DBUS_TYPE_INVALID));
+
+ if (!(card = pa_namereg_get(c->core, card_name, PA_NAMEREG_CARD))) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such card.");
+ return;
+ }
+
+ pa_assert_se((dbus_card = pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(card->index))));
+
+ object_path = pa_dbusiface_card_get_path(dbus_card);
+
+ pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_sink_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ char *sink_name;
+ pa_sink *sink;
+ pa_dbusiface_device *dbus_sink;
+ const char *object_path;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &sink_name, DBUS_TYPE_INVALID));
+
+ if (!(sink = pa_namereg_get(c->core, sink_name, PA_NAMEREG_SINK))) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", sink_name);
+ return;
+ }
+
+ pa_assert_se((dbus_sink = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(sink->index))));
+
+ object_path = pa_dbusiface_device_get_path(dbus_sink);
+
+ pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_source_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ char *source_name;
+ pa_source *source;
+ pa_dbusiface_device *dbus_source;
+ const char *object_path;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &source_name, DBUS_TYPE_INVALID));
+
+ if (!(source = pa_namereg_get(c->core, source_name, PA_NAMEREG_SOURCE))) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", source_name);
+ return;
+ }
+
+ pa_assert_se((dbus_source = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(source->index))));
+
+ object_path = pa_dbusiface_device_get_path(dbus_source);
+
+ pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_sample_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ char *sample_name;
+ pa_scache_entry *sample;
+ pa_dbusiface_sample *dbus_sample;
+ const char *object_path;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &sample_name, DBUS_TYPE_INVALID));
+
+ if (!(sample = pa_namereg_get(c->core, sample_name, PA_NAMEREG_SAMPLE))) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such sample.");
+ return;
+ }
+
+ pa_assert_se((dbus_sample = pa_hashmap_get(c->samples, PA_UINT32_TO_PTR(sample->index))));
+
+ object_path = pa_dbusiface_sample_get_path(dbus_sample);
+
+ pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_upload_sample(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ DBusMessageIter msg_iter;
+ DBusMessageIter array_iter;
+ const char *name;
+ dbus_uint32_t sample_format;
+ dbus_uint32_t sample_rate;
+ const dbus_uint32_t *channels;
+ int n_channels;
+ const dbus_uint32_t *default_volume;
+ int n_volume_entries;
+ pa_proplist *property_list;
+ const uint8_t *data;
+ int data_length;
+ int i;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_memchunk chunk;
+ uint32_t idx;
+ pa_dbusiface_sample *dbus_sample = NULL;
+ pa_scache_entry *sample = NULL;
+ const char *object_path;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ chunk.memblock = NULL;
+
+ pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+ dbus_message_iter_get_basic(&msg_iter, &name);
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ dbus_message_iter_get_basic(&msg_iter, &sample_format);
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ dbus_message_iter_get_basic(&msg_iter, &sample_rate);
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ dbus_message_iter_recurse(&msg_iter, &array_iter);
+ dbus_message_iter_get_fixed_array(&array_iter, &channels, &n_channels);
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ dbus_message_iter_recurse(&msg_iter, &array_iter);
+ dbus_message_iter_get_fixed_array(&array_iter, &default_volume, &n_volume_entries);
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter)))
+ return;
+
+ dbus_message_iter_recurse(&msg_iter, &array_iter);
+ dbus_message_iter_get_fixed_array(&array_iter, &data, &data_length);
+
+ if (sample_format >= PA_SAMPLE_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample format.");
+ goto finish;
+ }
+
+ if (sample_rate <= 0 || sample_rate > PA_RATE_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample rate.");
+ goto finish;
+ }
+
+ if (n_channels <= 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty channel map.");
+ goto finish;
+ }
+
+ if (n_channels > (int) PA_CHANNELS_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+ "Too many channels: %i. The maximum is %u.", n_channels, PA_CHANNELS_MAX);
+ goto finish;
+ }
+
+ for (i = 0; i < n_channels; ++i) {
+ if (channels[i] >= PA_CHANNEL_POSITION_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position.");
+ goto finish;
+ }
+ }
+
+ if (n_volume_entries != 0 && n_volume_entries != n_channels) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+ "The channels and default_volume arguments have different number of elements (%i and %i, resp).",
+ n_channels, n_volume_entries);
+ goto finish;
+ }
+
+ for (i = 0; i < n_volume_entries; ++i) {
+ if (default_volume[i] > PA_VOLUME_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u.", default_volume[i]);
+ goto finish;
+ }
+ }
+
+ if (data_length == 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty data.");
+ goto finish;
+ }
+
+ if (data_length > PA_SCACHE_ENTRY_SIZE_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+ "Too big sample: %i bytes. The maximum sample length is %u bytes.",
+ data_length, PA_SCACHE_ENTRY_SIZE_MAX);
+ goto finish;
+ }
+
+ ss.format = sample_format;
+ ss.rate = sample_rate;
+ ss.channels = n_channels;
+
+ pa_assert(pa_sample_spec_valid(&ss));
+
+ if (!pa_frame_aligned(data_length, &ss)) {
+ char buf[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+ "The sample length (%i bytes) doesn't align with the sample format and channels (%s).",
+ data_length, pa_sample_spec_snprint(buf, sizeof(buf), &ss));
+ goto finish;
+ }
+
+ map.channels = n_channels;
+ for (i = 0; i < n_channels; ++i)
+ map.map[i] = channels[i];
+
+ chunk.memblock = pa_memblock_new(c->core->mempool, data_length);
+ chunk.index = 0;
+ chunk.length = data_length;
+
+ memcpy(pa_memblock_acquire(chunk.memblock), data, data_length);
+ pa_memblock_release(chunk.memblock);
+
+ if (pa_scache_add_item(c->core, name, &ss, &map, &chunk, property_list, &idx) < 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Adding the sample failed.");
+ goto finish;
+ }
+
+ sample = pa_idxset_get_by_index(c->core->scache, idx);
+
+ if (n_volume_entries > 0) {
+ sample->volume.channels = n_channels;
+ for (i = 0; i < n_volume_entries; ++i)
+ sample->volume.values[i] = default_volume[i];
+ sample->volume_is_set = TRUE;
+ } else {
+ sample->volume_is_set = FALSE;
+ }
+
+ dbus_sample = pa_dbusiface_sample_new(c, sample);
+ pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), dbus_sample);
+
+ object_path = pa_dbusiface_sample_get_path(dbus_sample);
+
+ pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+
+finish:
+ if (property_list)
+ pa_proplist_free(property_list);
+
+ if (chunk.memblock)
+ pa_memblock_unref(chunk.memblock);
+}
+
+static pa_bool_t contains_space(const char *string) {
+ const char *p;
+
+ pa_assert(string);
+
+ for (p = string; *p; ++p) {
+ if (isspace(*p))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void handle_load_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ DBusMessageIter dict_entry_iter;
+ char *name = NULL;
+ const char *key = NULL;
+ const char *value = NULL;
+ char *escaped_value = NULL;
+ pa_strbuf *arg_buffer = NULL;
+ char *arg_string = NULL;
+ pa_module *module = NULL;
+ pa_dbusiface_module *dbus_module = NULL;
+ const char *object_path = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ if (c->core->disallow_module_loading) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow module loading.");
+ return;
+ }
+
+ pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+ dbus_message_iter_get_basic(&msg_iter, &name);
+
+ arg_buffer = pa_strbuf_new();
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ dbus_message_iter_recurse(&msg_iter, &dict_iter);
+
+ while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) {
+ if (!pa_strbuf_isempty(arg_buffer))
+ pa_strbuf_putc(arg_buffer, ' ');
+
+ dbus_message_iter_recurse(&dict_iter, &dict_entry_iter);
+
+ dbus_message_iter_get_basic(&dict_entry_iter, &key);
+
+ if (strlen(key) <= 0 || !pa_ascii_valid(key) || contains_space(key)) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid module argument name: %s", key);
+ goto finish;
+ }
+
+ pa_assert_se(dbus_message_iter_next(&dict_entry_iter));
+ dbus_message_iter_get_basic(&dict_entry_iter, &value);
+
+ escaped_value = pa_escape(value, "\"");
+ pa_strbuf_printf(arg_buffer, "%s=\"%s\"", key, escaped_value);
+ pa_xfree(escaped_value);
+
+ dbus_message_iter_next(&dict_iter);
+ }
+
+ arg_string = pa_strbuf_tostring(arg_buffer);
+
+ if (!(module = pa_module_load(c->core, name, arg_string))) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Failed to load module.");
+ goto finish;
+ }
+
+ dbus_module = pa_dbusiface_module_new(module);
+ pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(module->index), dbus_module);
+
+ object_path = pa_dbusiface_module_get_path(dbus_module);
+
+ pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+
+finish:
+ if (arg_buffer)
+ pa_strbuf_free(arg_buffer);
+
+ pa_xfree(arg_string);
+}
+
+static void handle_exit(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ if (c->core->disallow_exit) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow exiting.");
+ return;
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+ pa_core_exit(c->core, FALSE, 0);
+}
+
+static void handle_listen_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char *signal;
+ char **objects = NULL;
+ int n_objects;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_assert_se(dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &signal,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &objects, &n_objects,
+ DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_add_signal_listener(c->dbus_protocol, conn, *signal ? signal : NULL, objects, n_objects);
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+ dbus_free_string_array(objects);
+}
+
+static void handle_stop_listening_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ const char *signal;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+
+ pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &signal, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_remove_signal_listener(c->dbus_protocol, conn, *signal ? signal : NULL);
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ pa_dbusiface_core *c = userdata;
+ pa_dbusiface_card *card_iface = NULL;
+ pa_dbusiface_device *device_iface = NULL;
+ pa_dbusiface_stream *stream_iface = NULL;
+ pa_dbusiface_sample *sample_iface = NULL;
+ pa_dbusiface_module *module_iface = NULL;
+ pa_dbusiface_client *client_iface = NULL;
+ DBusMessage *signal = NULL;
+ const char *object_path = NULL;
+ pa_sink *new_fallback_sink = NULL;
+ pa_source *new_fallback_source = NULL;
+
+ pa_assert(c);
+
+ switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+ case PA_SUBSCRIPTION_EVENT_SERVER:
+ new_fallback_sink = pa_namereg_get_default_sink(core);
+ new_fallback_source = pa_namereg_get_default_source(core);
+
+ if (c->fallback_sink != new_fallback_sink) {
+ if (c->fallback_sink)
+ pa_sink_unref(c->fallback_sink);
+ c->fallback_sink = new_fallback_sink ? pa_sink_ref(new_fallback_sink) : NULL;
+
+ if (new_fallback_sink
+ && (device_iface = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(new_fallback_sink->index)))) {
+ object_path = pa_dbusiface_device_get_path(device_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_FALLBACK_SINK_UPDATED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+
+ } else if (!new_fallback_sink) {
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_FALLBACK_SINK_UNSET].name)));
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+ }
+
+ if (c->fallback_source != new_fallback_source) {
+ if (c->fallback_source)
+ pa_source_unref(c->fallback_source);
+ c->fallback_source = new_fallback_source ? pa_source_ref(new_fallback_source) : NULL;
+
+ if (new_fallback_source
+ && (device_iface = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(new_fallback_source->index)))) {
+ object_path = pa_dbusiface_device_get_path(device_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_FALLBACK_SOURCE_UPDATED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+
+ } else if (!new_fallback_source) {
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_FALLBACK_SOURCE_UNSET].name)));
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_CARD:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ if (!(card_iface = pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(idx)))) {
+ pa_card *card = NULL;
+
+ if (!(card = pa_idxset_get_by_index(core->cards, idx)))
+ return; /* The card was removed immediately after creation. */
+
+ card_iface = pa_dbusiface_card_new(c, card);
+ pa_hashmap_put(c->cards, PA_UINT32_TO_PTR(idx), card_iface);
+ }
+
+ object_path = pa_dbusiface_card_get_path(card_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_NEW_CARD].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (!(card_iface = pa_hashmap_remove(c->cards, PA_UINT32_TO_PTR(idx))))
+ return;
+
+ object_path = pa_dbusiface_card_get_path(card_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_CARD_REMOVED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbusiface_card_free(card_iface);
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_SINK:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_sink *sink = NULL;
+
+ if (!(sink = pa_idxset_get_by_index(core->sinks, idx)))
+ return; /* The sink was removed immediately after creation. */
+
+ if (!(device_iface = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(idx)))) {
+ device_iface = pa_dbusiface_device_new_sink(c, sink);
+ pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(idx), device_iface);
+ pa_hashmap_put(c->sinks_by_path, pa_dbusiface_device_get_path(device_iface), device_iface);
+ }
+
+ object_path = pa_dbusiface_device_get_path(device_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_NEW_SINK].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+
+ if (c->fallback_sink && pa_streq(c->fallback_sink->name, sink->name)) {
+ /* We have got default sink change event, but at that point
+ * the D-Bus sink object wasn't created yet. Now that the
+ * object is created, let's send the fallback sink change
+ * signal. */
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_FALLBACK_SINK_UPDATED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (!(device_iface = pa_hashmap_remove(c->sinks_by_index, PA_UINT32_TO_PTR(idx))))
+ return;
+
+ object_path = pa_dbusiface_device_get_path(device_iface);
+ pa_assert_se(pa_hashmap_remove(c->sinks_by_path, object_path));
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_SINK_REMOVED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbusiface_device_free(device_iface);
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_SOURCE:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_source *source = pa_idxset_get_by_index(core->sources, idx);
+
+ if (!(source = pa_idxset_get_by_index(core->sources, idx)))
+ return; /* The source was removed immediately after creation. */
+
+ if (!(device_iface = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(idx)))) {
+ device_iface = pa_dbusiface_device_new_source(c, source);
+ pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(idx), device_iface);
+ pa_hashmap_put(c->sources_by_path, pa_dbusiface_device_get_path(device_iface), device_iface);
+ }
+
+ object_path = pa_dbusiface_device_get_path(device_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_NEW_SOURCE].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+
+ if (c->fallback_source && pa_streq(c->fallback_source->name, source->name)) {
+ /* We have got default source change event, but at that
+ * point the D-Bus source object wasn't created yet. Now
+ * that the object is created, let's send the fallback
+ * source change signal. */
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_FALLBACK_SOURCE_UPDATED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (!(device_iface = pa_hashmap_remove(c->sources_by_index, PA_UINT32_TO_PTR(idx))))
+ return;
+
+ object_path = pa_dbusiface_device_get_path(device_iface);
+ pa_assert_se(pa_hashmap_remove(c->sources_by_path, object_path));
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_SOURCE_REMOVED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbusiface_device_free(device_iface);
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_sink_input *sink_input = NULL;
+
+ if (!(sink_input = pa_idxset_get_by_index(core->sink_inputs, idx)))
+ return; /* The sink input was removed immediately after creation. */
+
+ if (!(stream_iface = pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(idx)))) {
+ stream_iface = pa_dbusiface_stream_new_playback(c, sink_input);
+ pa_hashmap_put(c->playback_streams, PA_UINT32_TO_PTR(idx), stream_iface);
+ }
+
+ object_path = pa_dbusiface_stream_get_path(stream_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_NEW_PLAYBACK_STREAM].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (!(stream_iface = pa_hashmap_remove(c->playback_streams, PA_UINT32_TO_PTR(idx))))
+ return;
+
+ object_path = pa_dbusiface_stream_get_path(stream_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_PLAYBACK_STREAM_REMOVED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbusiface_stream_free(stream_iface);
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_source_output *source_output = NULL;
+
+ if (!(source_output = pa_idxset_get_by_index(core->source_outputs, idx)))
+ return; /* The source output was removed immediately after creation. */
+
+ if (!(stream_iface = pa_hashmap_get(c->record_streams, PA_UINT32_TO_PTR(idx)))) {
+ stream_iface = pa_dbusiface_stream_new_record(c, source_output);
+ pa_hashmap_put(c->record_streams, PA_UINT32_TO_PTR(idx), stream_iface);
+ }
+
+ object_path = pa_dbusiface_stream_get_path(stream_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_NEW_RECORD_STREAM].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (!(stream_iface = pa_hashmap_remove(c->record_streams, PA_UINT32_TO_PTR(idx))))
+ return;
+
+ object_path = pa_dbusiface_stream_get_path(stream_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_RECORD_STREAM_REMOVED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbusiface_stream_free(stream_iface);
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_scache_entry *sample = NULL;
+
+ if (!(sample = pa_idxset_get_by_index(core->scache, idx)))
+ return; /* The sample was removed immediately after creation. */
+
+ if (!(sample_iface = pa_hashmap_get(c->samples, PA_UINT32_TO_PTR(idx)))) {
+ sample_iface = pa_dbusiface_sample_new(c, sample);
+ pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), sample_iface);
+ }
+
+ object_path = pa_dbusiface_sample_get_path(sample_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_NEW_SAMPLE].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (!(sample_iface = pa_hashmap_remove(c->samples, PA_UINT32_TO_PTR(idx))))
+ return;
+
+ object_path = pa_dbusiface_sample_get_path(sample_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_SAMPLE_REMOVED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbusiface_sample_free(sample_iface);
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_MODULE:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_module *module = NULL;
+
+ if (!(module = pa_idxset_get_by_index(core->modules, idx)))
+ return; /* The module was removed immediately after creation. */
+
+ if (!(module_iface = pa_hashmap_get(c->modules, PA_UINT32_TO_PTR(idx)))) {
+ module_iface = pa_dbusiface_module_new(module);
+ pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(idx), module_iface);
+ }
+
+ object_path = pa_dbusiface_module_get_path(module_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_NEW_MODULE].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (!(module_iface = pa_hashmap_remove(c->modules, PA_UINT32_TO_PTR(idx))))
+ return;
+
+ object_path = pa_dbusiface_module_get_path(module_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_MODULE_REMOVED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbusiface_module_free(module_iface);
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_CLIENT:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_client *client = NULL;
+
+ if (!(client = pa_idxset_get_by_index(core->clients, idx)))
+ return; /* The client was removed immediately after creation. */
+
+ if (!(client_iface = pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(idx)))) {
+ client_iface = pa_dbusiface_client_new(c, client);
+ pa_hashmap_put(c->clients, PA_UINT32_TO_PTR(idx), client_iface);
+ }
+
+ object_path = pa_dbusiface_client_get_path(client_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_NEW_CLIENT].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (!(client_iface = pa_hashmap_remove(c->clients, PA_UINT32_TO_PTR(idx))))
+ return;
+
+ object_path = pa_dbusiface_client_get_path(client_iface);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_CLIENT_REMOVED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbusiface_client_free(client_iface);
+ }
+ break;
+ }
+
+ if (signal) {
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ }
+}
+
+static pa_hook_result_t extension_registered_cb(void *hook_data, void *call_data, void *slot_data) {
+ pa_dbusiface_core *c = slot_data;
+ const char *ext_name = call_data;
+ DBusMessage *signal = NULL;
+
+ pa_assert(c);
+ pa_assert(ext_name);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_NEW_EXTENSION].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_STRING, &ext_name, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t extension_unregistered_cb(void *hook_data, void *call_data, void *slot_data) {
+ pa_dbusiface_core *c = slot_data;
+ const char *ext_name = call_data;
+ DBusMessage *signal = NULL;
+
+ pa_assert(c);
+ pa_assert(ext_name);
+
+ pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+ PA_DBUS_CORE_INTERFACE,
+ signals[SIGNAL_EXTENSION_REMOVED].name)));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_STRING, &ext_name, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+ dbus_message_unref(signal);
+
+ return PA_HOOK_OK;
+}
+
+pa_dbusiface_core *pa_dbusiface_core_new(pa_core *core) {
+ pa_dbusiface_core *c;
+ pa_card *card;
+ pa_sink *sink;
+ pa_source *source;
+ pa_dbusiface_device *device;
+ pa_sink_input *sink_input;
+ pa_source_output *source_output;
+ pa_scache_entry *sample;
+ pa_module *module;
+ pa_client *client;
+ uint32_t idx;
+
+ pa_assert(core);
+
+ c = pa_xnew(pa_dbusiface_core, 1);
+ c->core = pa_core_ref(core);
+ c->subscription = pa_subscription_new(core, PA_SUBSCRIPTION_MASK_ALL, subscription_cb, c);
+ c->dbus_protocol = pa_dbus_protocol_get(core);
+ c->cards = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ c->sinks_by_index = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ c->sinks_by_path = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ c->sources_by_index = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ c->sources_by_path = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ c->playback_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ c->record_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ c->samples = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ c->modules = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ c->clients = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ c->fallback_sink = pa_namereg_get_default_sink(core);
+ c->fallback_source = pa_namereg_get_default_source(core);
+ c->extension_registered_slot = pa_dbus_protocol_hook_connect(c->dbus_protocol,
+ PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED,
+ PA_HOOK_NORMAL,
+ extension_registered_cb,
+ c);
+ c->extension_unregistered_slot = pa_dbus_protocol_hook_connect(c->dbus_protocol,
+ PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED,
+ PA_HOOK_NORMAL,
+ extension_unregistered_cb,
+ c);
+ c->memstats = pa_dbusiface_memstats_new(c, core);
+
+ if (c->fallback_sink)
+ pa_sink_ref(c->fallback_sink);
+ if (c->fallback_source)
+ pa_source_ref(c->fallback_source);
+
+ PA_IDXSET_FOREACH(card, core->cards, idx)
+ pa_hashmap_put(c->cards, PA_UINT32_TO_PTR(idx), pa_dbusiface_card_new(c, card));
+
+ PA_IDXSET_FOREACH(sink, core->sinks, idx) {
+ device = pa_dbusiface_device_new_sink(c, sink);
+ pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(idx), device);
+ pa_hashmap_put(c->sinks_by_path, pa_dbusiface_device_get_path(device), device);
+ }
+
+ PA_IDXSET_FOREACH(source, core->sources, idx) {
+ device = pa_dbusiface_device_new_source(c, source);
+ pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(idx), device);
+ pa_hashmap_put(c->sources_by_path, pa_dbusiface_device_get_path(device), device);
+ }
+
+ PA_IDXSET_FOREACH(sink_input, core->sink_inputs, idx)
+ pa_hashmap_put(c->playback_streams, PA_UINT32_TO_PTR(idx), pa_dbusiface_stream_new_playback(c, sink_input));
+
+ PA_IDXSET_FOREACH(source_output, core->source_outputs, idx)
+ pa_hashmap_put(c->record_streams, PA_UINT32_TO_PTR(idx), pa_dbusiface_stream_new_record(c, source_output));
+
+ PA_IDXSET_FOREACH(sample, core->scache, idx)
+ pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), pa_dbusiface_sample_new(c, sample));
+
+ PA_IDXSET_FOREACH(module, core->modules, idx)
+ pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(idx), pa_dbusiface_module_new(module));
+
+ PA_IDXSET_FOREACH(client, core->clients, idx)
+ pa_hashmap_put(c->clients, PA_UINT32_TO_PTR(idx), pa_dbusiface_client_new(c, client));
+
+ pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, PA_DBUS_CORE_OBJECT_PATH, &core_interface_info, c) >= 0);
+
+ return c;
+}
+
+static void free_card_cb(void *p, void *userdata) {
+ pa_dbusiface_card *c = p;
+
+ pa_assert(c);
+
+ pa_dbusiface_card_free(c);
+}
+
+static void free_device_cb(void *p, void *userdata) {
+ pa_dbusiface_device *d = p;
+
+ pa_assert(d);
+
+ pa_dbusiface_device_free(d);
+}
+
+static void free_stream_cb(void *p, void *userdata) {
+ pa_dbusiface_stream *s = p;
+
+ pa_assert(s);
+
+ pa_dbusiface_stream_free(s);
+}
+
+static void free_sample_cb(void *p, void *userdata) {
+ pa_dbusiface_sample *s = p;
+
+ pa_assert(s);
+
+ pa_dbusiface_sample_free(s);
+}
+
+static void free_module_cb(void *p, void *userdata) {
+ pa_dbusiface_module *m = p;
+
+ pa_assert(m);
+
+ pa_dbusiface_module_free(m);
+}
+
+static void free_client_cb(void *p, void *userdata) {
+ pa_dbusiface_client *c = p;
+
+ pa_assert(c);
+
+ pa_dbusiface_client_free(c);
+}
+
+void pa_dbusiface_core_free(pa_dbusiface_core *c) {
+ pa_assert(c);
+
+ pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, PA_DBUS_CORE_OBJECT_PATH, core_interface_info.name) >= 0);
+
+ pa_subscription_free(c->subscription);
+ pa_hashmap_free(c->cards, free_card_cb, NULL);
+ pa_hashmap_free(c->sinks_by_index, free_device_cb, NULL);
+ pa_hashmap_free(c->sinks_by_path, NULL, NULL);
+ pa_hashmap_free(c->sources_by_index, free_device_cb, NULL);
+ pa_hashmap_free(c->sources_by_path, NULL, NULL);
+ pa_hashmap_free(c->playback_streams, free_stream_cb, NULL);
+ pa_hashmap_free(c->record_streams, free_stream_cb, NULL);
+ pa_hashmap_free(c->samples, free_sample_cb, NULL);
+ pa_hashmap_free(c->modules, free_module_cb, NULL);
+ pa_hashmap_free(c->clients, free_client_cb, NULL);
+ pa_hook_slot_free(c->extension_registered_slot);
+ pa_hook_slot_free(c->extension_unregistered_slot);
+ pa_dbusiface_memstats_free(c->memstats);
+
+ if (c->fallback_sink)
+ pa_sink_unref(c->fallback_sink);
+ if (c->fallback_source)
+ pa_source_unref(c->fallback_source);
+
+ pa_dbus_protocol_unref(c->dbus_protocol);
+ pa_core_unref(c->core);
+
+ pa_xfree(c);
+}
+
+const char *pa_dbusiface_core_get_card_path(pa_dbusiface_core *c, const pa_card *card) {
+ pa_assert(c);
+ pa_assert(card);
+
+ return pa_dbusiface_card_get_path(pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(card->index)));
+}
+
+const char *pa_dbusiface_core_get_sink_path(pa_dbusiface_core *c, const pa_sink *sink) {
+ pa_assert(c);
+ pa_assert(sink);
+
+ return pa_dbusiface_device_get_path(pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(sink->index)));
+}
+
+const char *pa_dbusiface_core_get_source_path(pa_dbusiface_core *c, const pa_source *source) {
+ pa_assert(c);
+ pa_assert(source);
+
+ return pa_dbusiface_device_get_path(pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(source->index)));
+}
+
+const char *pa_dbusiface_core_get_playback_stream_path(pa_dbusiface_core *c, const pa_sink_input *sink_input) {
+ pa_assert(c);
+ pa_assert(sink_input);
+
+ return pa_dbusiface_stream_get_path(pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(sink_input->index)));
+}
+
+const char *pa_dbusiface_core_get_record_stream_path(pa_dbusiface_core *c, const pa_source_output *source_output) {
+ pa_assert(c);
+ pa_assert(source_output);
+
+ return pa_dbusiface_stream_get_path(pa_hashmap_get(c->record_streams, PA_UINT32_TO_PTR(source_output->index)));
+}
+
+const char *pa_dbusiface_core_get_module_path(pa_dbusiface_core *c, const pa_module *module) {
+ pa_assert(c);
+ pa_assert(module);
+
+ return pa_dbusiface_module_get_path(pa_hashmap_get(c->modules, PA_UINT32_TO_PTR(module->index)));
+}
+
+const char *pa_dbusiface_core_get_client_path(pa_dbusiface_core *c, const pa_client *client) {
+ pa_assert(c);
+ pa_assert(client);
+
+ return pa_dbusiface_client_get_path(pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(client->index)));
+}
+
+pa_sink *pa_dbusiface_core_get_sink(pa_dbusiface_core *c, const char *object_path) {
+ pa_dbusiface_device *device = NULL;
+
+ pa_assert(c);
+ pa_assert(object_path);
+
+ device = pa_hashmap_get(c->sinks_by_path, object_path);
+
+ if (device)
+ return pa_dbusiface_device_get_sink(device);
+ else
+ return NULL;
+}
+
+pa_source *pa_dbusiface_core_get_source(pa_dbusiface_core *c, const char *object_path) {
+ pa_dbusiface_device *device = NULL;
+
+ pa_assert(c);
+ pa_assert(object_path);
+
+ device = pa_hashmap_get(c->sources_by_path, object_path);
+
+ if (device)
+ return pa_dbusiface_device_get_source(device);
+ else
+ return NULL;
+}
diff --git a/src/modules/dbus/iface-core.h b/src/modules/dbus/iface-core.h
new file mode 100644
index 00000000..900b6d1c
--- /dev/null
+++ b/src/modules/dbus/iface-core.h
@@ -0,0 +1,52 @@
+#ifndef foodbusifacecorehfoo
+#define foodbusifacecorehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ 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.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Core interface
+ * documentation.
+ */
+
+#include <pulsecore/core.h>
+
+typedef struct pa_dbusiface_core pa_dbusiface_core;
+
+pa_dbusiface_core *pa_dbusiface_core_new(pa_core *core);
+void pa_dbusiface_core_free(pa_dbusiface_core *c);
+
+const char *pa_dbusiface_core_get_card_path(pa_dbusiface_core *c, const pa_card *card);
+const char *pa_dbusiface_core_get_sink_path(pa_dbusiface_core *c, const pa_sink *sink);
+const char *pa_dbusiface_core_get_source_path(pa_dbusiface_core *c, const pa_source *source);
+const char *pa_dbusiface_core_get_playback_stream_path(pa_dbusiface_core *c, const pa_sink_input *sink_input);
+const char *pa_dbusiface_core_get_record_stream_path(pa_dbusiface_core *c, const pa_source_output *source_output);
+const char *pa_dbusiface_core_get_module_path(pa_dbusiface_core *c, const pa_module *module);
+const char *pa_dbusiface_core_get_client_path(pa_dbusiface_core *c, const pa_client *client);
+
+/* Returns NULL if there's no sink with the given path. */
+pa_sink *pa_dbusiface_core_get_sink(pa_dbusiface_core *c, const char *object_path);
+
+/* Returns NULL if there's no source with the given path. */
+pa_source *pa_dbusiface_core_get_source(pa_dbusiface_core *c, const char *object_path);
+
+#endif
diff --git a/src/modules/dbus/iface-device-port.c b/src/modules/dbus/iface-device-port.c
new file mode 100644
index 00000000..d403b6a2
--- /dev/null
+++ b/src/modules/dbus/iface-device-port.c
@@ -0,0 +1,190 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+
+#include "iface-device-port.h"
+
+#define OBJECT_NAME "port"
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+struct pa_dbusiface_device_port {
+ uint32_t index;
+ pa_device_port *port;
+ char *path;
+ pa_dbus_protocol *dbus_protocol;
+};
+
+enum property_handler_index {
+ PROPERTY_HANDLER_INDEX,
+ PROPERTY_HANDLER_NAME,
+ PROPERTY_HANDLER_DESCRIPTION,
+ PROPERTY_HANDLER_PRIORITY,
+ PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+ [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL },
+ [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL },
+ [PROPERTY_HANDLER_DESCRIPTION] = { .property_name = "Description", .type = "s", .get_cb = handle_get_description, .set_cb = NULL },
+ [PROPERTY_HANDLER_PRIORITY] = { .property_name = "Priority", .type = "u", .get_cb = handle_get_priority, .set_cb = NULL },
+};
+
+static pa_dbus_interface_info port_interface_info = {
+ .name = PA_DBUSIFACE_DEVICE_PORT_INTERFACE,
+ .method_handlers = NULL,
+ .n_method_handlers = 0,
+ .property_handlers = property_handlers,
+ .n_property_handlers = PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_get_all,
+ .signals = NULL,
+ .n_signals = 0
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device_port *p = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &p->index);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device_port *p = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->port->name);
+}
+
+static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device_port *p = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->port->description);
+}
+
+static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device_port *p = userdata;
+ dbus_uint32_t priority = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ priority = p->port->priority;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &priority);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device_port *p = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ dbus_uint32_t priority = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(p);
+
+ priority = p->port->priority;
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &p->index);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &p->port->name);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DESCRIPTION].property_name, DBUS_TYPE_STRING, &p->port->description);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PRIORITY].property_name, DBUS_TYPE_UINT32, &priority);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+ dbus_message_unref(reply);
+}
+
+pa_dbusiface_device_port *pa_dbusiface_device_port_new(
+ pa_dbusiface_device *device,
+ pa_core *core,
+ pa_device_port *port,
+ uint32_t idx) {
+ pa_dbusiface_device_port *p = NULL;
+
+ pa_assert(device);
+ pa_assert(core);
+ pa_assert(port);
+
+ p = pa_xnew(pa_dbusiface_device_port, 1);
+ p->index = idx;
+ p->port = port;
+ p->path = pa_sprintf_malloc("%s/%s%u", pa_dbusiface_device_get_path(device), OBJECT_NAME, idx);
+ p->dbus_protocol = pa_dbus_protocol_get(core);
+
+ pa_assert_se(pa_dbus_protocol_add_interface(p->dbus_protocol, p->path, &port_interface_info, p) >= 0);
+
+ return p;
+}
+
+void pa_dbusiface_device_port_free(pa_dbusiface_device_port *p) {
+ pa_assert(p);
+
+ pa_assert_se(pa_dbus_protocol_remove_interface(p->dbus_protocol, p->path, port_interface_info.name) >= 0);
+
+ pa_dbus_protocol_unref(p->dbus_protocol);
+
+ pa_xfree(p->path);
+ pa_xfree(p);
+}
+
+const char *pa_dbusiface_device_port_get_path(pa_dbusiface_device_port *p) {
+ pa_assert(p);
+
+ return p->path;
+}
+
+const char *pa_dbusiface_device_port_get_name(pa_dbusiface_device_port *p) {
+ pa_assert(p);
+
+ return p->port->name;
+}
diff --git a/src/modules/dbus/iface-device-port.h b/src/modules/dbus/iface-device-port.h
new file mode 100644
index 00000000..0461e2ff
--- /dev/null
+++ b/src/modules/dbus/iface-device-port.h
@@ -0,0 +1,50 @@
+#ifndef foodbusifacedeviceporthfoo
+#define foodbusifacedeviceporthfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ 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.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.DevicePort.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the DevicePort interface
+ * documentation.
+ */
+
+#include <pulsecore/protocol-dbus.h>
+#include <pulsecore/sink.h>
+
+#include "iface-device.h"
+
+#define PA_DBUSIFACE_DEVICE_PORT_INTERFACE PA_DBUS_CORE_INTERFACE ".DevicePort"
+
+typedef struct pa_dbusiface_device_port pa_dbusiface_device_port;
+
+pa_dbusiface_device_port *pa_dbusiface_device_port_new(
+ pa_dbusiface_device *device,
+ pa_core *core,
+ pa_device_port *port,
+ uint32_t idx);
+void pa_dbusiface_device_port_free(pa_dbusiface_device_port *p);
+
+const char *pa_dbusiface_device_port_get_path(pa_dbusiface_device_port *p);
+const char *pa_dbusiface_device_port_get_name(pa_dbusiface_device_port *p);
+
+#endif
diff --git a/src/modules/dbus/iface-device.c b/src/modules/dbus/iface-device.c
new file mode 100644
index 00000000..3a747a44
--- /dev/null
+++ b/src/modules/dbus/iface-device.c
@@ -0,0 +1,1316 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-device-port.h"
+
+#include "iface-device.h"
+
+#define SINK_OBJECT_NAME "sink"
+#define SOURCE_OBJECT_NAME "source"
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_card(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_has_flat_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_has_convertible_to_decibel_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_base_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_volume_steps(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_has_hardware_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_has_hardware_mute(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_configured_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_has_dynamic_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_is_hardware_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_is_network_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_state(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_ports(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_active_port(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_active_port(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_suspend(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_port_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_sink_get_monitor_source(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_sink_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_source_get_monitor_of_sink(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_source_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum device_type {
+ DEVICE_TYPE_SINK,
+ DEVICE_TYPE_SOURCE
+};
+
+struct pa_dbusiface_device {
+ pa_dbusiface_core *core;
+
+ union {
+ pa_sink *sink;
+ pa_source *source;
+ };
+ enum device_type type;
+ char *path;
+ pa_cvolume volume;
+ dbus_bool_t mute;
+ union {
+ pa_sink_state_t sink_state;
+ pa_source_state_t source_state;
+ };
+ pa_hashmap *ports;
+ uint32_t next_port_index;
+ pa_device_port *active_port;
+ pa_proplist *proplist;
+
+ pa_dbus_protocol *dbus_protocol;
+ pa_subscription *subscription;
+};
+
+enum property_handler_index {
+ PROPERTY_HANDLER_INDEX,
+ PROPERTY_HANDLER_NAME,
+ PROPERTY_HANDLER_DRIVER,
+ PROPERTY_HANDLER_OWNER_MODULE,
+ PROPERTY_HANDLER_CARD,
+ PROPERTY_HANDLER_SAMPLE_FORMAT,
+ PROPERTY_HANDLER_SAMPLE_RATE,
+ PROPERTY_HANDLER_CHANNELS,
+ PROPERTY_HANDLER_VOLUME,
+ PROPERTY_HANDLER_HAS_FLAT_VOLUME,
+ PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME,
+ PROPERTY_HANDLER_BASE_VOLUME,
+ PROPERTY_HANDLER_VOLUME_STEPS,
+ PROPERTY_HANDLER_MUTE,
+ PROPERTY_HANDLER_HAS_HARDWARE_VOLUME,
+ PROPERTY_HANDLER_HAS_HARDWARE_MUTE,
+ PROPERTY_HANDLER_CONFIGURED_LATENCY,
+ PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY,
+ PROPERTY_HANDLER_LATENCY,
+ PROPERTY_HANDLER_IS_HARDWARE_DEVICE,
+ PROPERTY_HANDLER_IS_NETWORK_DEVICE,
+ PROPERTY_HANDLER_STATE,
+ PROPERTY_HANDLER_PORTS,
+ PROPERTY_HANDLER_ACTIVE_PORT,
+ PROPERTY_HANDLER_PROPERTY_LIST,
+ PROPERTY_HANDLER_MAX
+};
+
+enum sink_property_handler_index {
+ SINK_PROPERTY_HANDLER_MONITOR_SOURCE,
+ SINK_PROPERTY_HANDLER_MAX
+};
+
+enum source_property_handler_index {
+ SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK,
+ SOURCE_PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+ [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL },
+ [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL },
+ [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL },
+ [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL },
+ [PROPERTY_HANDLER_CARD] = { .property_name = "Card", .type = "o", .get_cb = handle_get_card, .set_cb = NULL },
+ [PROPERTY_HANDLER_SAMPLE_FORMAT] = { .property_name = "SampleFormat", .type = "u", .get_cb = handle_get_sample_format, .set_cb = NULL },
+ [PROPERTY_HANDLER_SAMPLE_RATE] = { .property_name = "SampleRate", .type = "u", .get_cb = handle_get_sample_rate, .set_cb = NULL },
+ [PROPERTY_HANDLER_CHANNELS] = { .property_name = "Channels", .type = "au", .get_cb = handle_get_channels, .set_cb = NULL },
+ [PROPERTY_HANDLER_VOLUME] = { .property_name = "Volume", .type = "au", .get_cb = handle_get_volume, .set_cb = handle_set_volume },
+ [PROPERTY_HANDLER_HAS_FLAT_VOLUME] = { .property_name = "HasFlatVolume", .type = "b", .get_cb = handle_get_has_flat_volume, .set_cb = NULL },
+ [PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME] = { .property_name = "HasConvertibleToDecibelVolume", .type = "b", .get_cb = handle_get_has_convertible_to_decibel_volume, .set_cb = NULL },
+ [PROPERTY_HANDLER_BASE_VOLUME] = { .property_name = "BaseVolume", .type = "u", .get_cb = handle_get_base_volume, .set_cb = NULL },
+ [PROPERTY_HANDLER_VOLUME_STEPS] = { .property_name = "VolumeSteps", .type = "u", .get_cb = handle_get_volume_steps, .set_cb = NULL },
+ [PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_get_mute, .set_cb = handle_set_mute },
+ [PROPERTY_HANDLER_HAS_HARDWARE_VOLUME] = { .property_name = "HasHardwareVolume", .type = "b", .get_cb = handle_get_has_hardware_volume, .set_cb = NULL },
+ [PROPERTY_HANDLER_HAS_HARDWARE_MUTE] = { .property_name = "HasHardwareMute", .type = "b", .get_cb = handle_get_has_hardware_mute, .set_cb = NULL },
+ [PROPERTY_HANDLER_CONFIGURED_LATENCY] = { .property_name = "ConfiguredLatency", .type = "t", .get_cb = handle_get_configured_latency, .set_cb = NULL },
+ [PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY] = { .property_name = "HasDynamicLatency", .type = "b", .get_cb = handle_get_has_dynamic_latency, .set_cb = NULL },
+ [PROPERTY_HANDLER_LATENCY] = { .property_name = "Latency", .type = "t", .get_cb = handle_get_latency, .set_cb = NULL },
+ [PROPERTY_HANDLER_IS_HARDWARE_DEVICE] = { .property_name = "IsHardwareDevice", .type = "b", .get_cb = handle_get_is_hardware_device, .set_cb = NULL },
+ [PROPERTY_HANDLER_IS_NETWORK_DEVICE] = { .property_name = "IsNetworkDevice", .type = "b", .get_cb = handle_get_is_network_device, .set_cb = NULL },
+ [PROPERTY_HANDLER_STATE] = { .property_name = "State", .type = "u", .get_cb = handle_get_state, .set_cb = NULL },
+ [PROPERTY_HANDLER_PORTS] = { .property_name = "Ports", .type = "ao", .get_cb = handle_get_ports, .set_cb = NULL },
+ [PROPERTY_HANDLER_ACTIVE_PORT] = { .property_name = "ActivePort", .type = "o", .get_cb = handle_get_active_port, .set_cb = handle_set_active_port },
+ [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL }
+};
+
+static pa_dbus_property_handler sink_property_handlers[SINK_PROPERTY_HANDLER_MAX] = {
+ [SINK_PROPERTY_HANDLER_MONITOR_SOURCE] = { .property_name = "MonitorSource", .type = "o", .get_cb = handle_sink_get_monitor_source, .set_cb = NULL }
+};
+
+static pa_dbus_property_handler source_property_handlers[SOURCE_PROPERTY_HANDLER_MAX] = {
+ [SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK] = { .property_name = "MonitorOfSink", .type = "o", .get_cb = handle_source_get_monitor_of_sink, .set_cb = NULL }
+};
+
+enum method_handler_index {
+ METHOD_HANDLER_SUSPEND,
+ METHOD_HANDLER_GET_PORT_BY_NAME,
+ METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info suspend_args[] = { { "suspend", "b", "in" } };
+static pa_dbus_arg_info get_port_by_name_args[] = { { "name", "s", "in" }, { "port", "o", "out" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+ [METHOD_HANDLER_SUSPEND] = {
+ .method_name = "Suspend",
+ .arguments = suspend_args,
+ .n_arguments = sizeof(suspend_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_suspend },
+ [METHOD_HANDLER_GET_PORT_BY_NAME] = {
+ .method_name = "GetPortByName",
+ .arguments = get_port_by_name_args,
+ .n_arguments = sizeof(get_port_by_name_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_get_port_by_name }
+};
+
+enum signal_index {
+ SIGNAL_VOLUME_UPDATED,
+ SIGNAL_MUTE_UPDATED,
+ SIGNAL_STATE_UPDATED,
+ SIGNAL_ACTIVE_PORT_UPDATED,
+ SIGNAL_PROPERTY_LIST_UPDATED,
+ SIGNAL_MAX
+};
+
+static pa_dbus_arg_info volume_updated_args[] = { { "volume", "au", NULL } };
+static pa_dbus_arg_info mute_updated_args[] = { { "muted", "b", NULL } };
+static pa_dbus_arg_info state_updated_args[] = { { "state", "u", NULL } };
+static pa_dbus_arg_info active_port_updated_args[] = { { "port", "o", NULL } };
+static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+ [SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = volume_updated_args, .n_arguments = 1 },
+ [SIGNAL_MUTE_UPDATED] = { .name = "MuteUpdated", .arguments = mute_updated_args, .n_arguments = 1 },
+ [SIGNAL_STATE_UPDATED] = { .name = "StateUpdated", .arguments = state_updated_args, .n_arguments = 1 },
+ [SIGNAL_ACTIVE_PORT_UPDATED] = { .name = "ActivePortUpdated", .arguments = active_port_updated_args, .n_arguments = 1 },
+ [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info device_interface_info = {
+ .name = PA_DBUSIFACE_DEVICE_INTERFACE,
+ .method_handlers = method_handlers,
+ .n_method_handlers = METHOD_HANDLER_MAX,
+ .property_handlers = property_handlers,
+ .n_property_handlers = PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_get_all,
+ .signals = signals,
+ .n_signals = SIGNAL_MAX
+};
+
+static pa_dbus_interface_info sink_interface_info = {
+ .name = PA_DBUSIFACE_SINK_INTERFACE,
+ .method_handlers = NULL,
+ .n_method_handlers = 0,
+ .property_handlers = sink_property_handlers,
+ .n_property_handlers = SINK_PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_sink_get_all,
+ .signals = NULL,
+ .n_signals = 0
+};
+
+static pa_dbus_interface_info source_interface_info = {
+ .name = PA_DBUSIFACE_SOURCE_INTERFACE,
+ .method_handlers = NULL,
+ .n_method_handlers = 0,
+ .property_handlers = source_property_handlers,
+ .n_property_handlers = SOURCE_PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_source_get_all,
+ .signals = NULL,
+ .n_signals = 0
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_uint32_t idx = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ idx = (d->type == DEVICE_TYPE_SINK) ? d->sink->index : d->source->index;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ const char *name = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ name = (d->type == DEVICE_TYPE_SINK) ? d->sink->name : d->source->name;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &name);
+}
+
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ const char *driver = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ driver = (d->type == DEVICE_TYPE_SINK) ? d->sink->driver : d->source->driver;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &driver);
+}
+
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ pa_module *owner_module = NULL;
+ const char *object_path = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ owner_module = (d->type == DEVICE_TYPE_SINK) ? d->sink->module : d->source->module;
+
+ if (!owner_module) {
+ if (d->type == DEVICE_TYPE_SINK)
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Sink %s doesn't have an owner module.", d->sink->name);
+ else
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Source %s doesn't have an owner module.", d->source->name);
+ return;
+ }
+
+ object_path = pa_dbusiface_core_get_module_path(d->core, owner_module);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_card(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ pa_card *card = NULL;
+ const char *object_path = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ card = (d->type == DEVICE_TYPE_SINK) ? d->sink->card : d->source->card;
+
+ if (!card) {
+ if (d->type == DEVICE_TYPE_SINK)
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Sink %s doesn't belong to any card.", d->sink->name);
+ else
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Source %s doesn't belong to any card.", d->source->name);
+ return;
+ }
+
+ object_path = pa_dbusiface_core_get_card_path(d->core, card);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_uint32_t sample_format = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ sample_format = (d->type == DEVICE_TYPE_SINK) ? d->sink->sample_spec.format : d->source->sample_spec.format;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format);
+}
+
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_uint32_t sample_rate = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ sample_rate = (d->type == DEVICE_TYPE_SINK) ? d->sink->sample_spec.rate : d->source->sample_spec.rate;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_rate);
+}
+
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ pa_channel_map *channel_map = NULL;
+ dbus_uint32_t channels[PA_CHANNELS_MAX];
+ unsigned i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ channel_map = (d->type == DEVICE_TYPE_SINK) ? &d->sink->channel_map : &d->source->channel_map;
+
+ for (i = 0; i < channel_map->channels; ++i)
+ channels[i] = channel_map->map[i];
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, channel_map->channels);
+}
+
+static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_uint32_t volume[PA_CHANNELS_MAX];
+ unsigned i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ for (i = 0; i < d->volume.channels; ++i)
+ volume[i] = d->volume.values[i];
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, volume, d->volume.channels);
+}
+
+static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ DBusMessageIter array_iter;
+ int device_channels = 0;
+ dbus_uint32_t *volume = NULL;
+ int n_volume_entries = 0;
+ pa_cvolume new_vol;
+ int i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(d);
+
+ pa_cvolume_init(&new_vol);
+
+ device_channels = (d->type == DEVICE_TYPE_SINK) ? d->sink->channel_map.channels : d->source->channel_map.channels;
+
+ new_vol.channels = device_channels;
+
+ dbus_message_iter_recurse(iter, &array_iter);
+ dbus_message_iter_get_fixed_array(&array_iter, &volume, &n_volume_entries);
+
+ if (n_volume_entries != device_channels) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+ "Expected %u volume entries, got %i.", device_channels, n_volume_entries);
+ return;
+ }
+
+ for (i = 0; i < n_volume_entries; ++i) {
+ if (volume[i] > PA_VOLUME_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too large volume value: %u", volume[i]);
+ return;
+ }
+ new_vol.values[i] = volume[i];
+ }
+
+ if (d->type == DEVICE_TYPE_SINK)
+ pa_sink_set_volume(d->sink, &new_vol, TRUE, TRUE);
+ else
+ pa_source_set_volume(d->source, &new_vol, TRUE);
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_has_flat_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_bool_t has_flat_volume = FALSE;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ has_flat_volume = (d->type == DEVICE_TYPE_SINK) ? (d->sink->flags & PA_SINK_FLAT_VOLUME) : FALSE;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_flat_volume);
+}
+
+static void handle_get_has_convertible_to_decibel_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_bool_t has_convertible_to_decibel_volume = FALSE;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ has_convertible_to_decibel_volume = (d->type == DEVICE_TYPE_SINK)
+ ? (d->sink->flags & PA_SINK_DECIBEL_VOLUME)
+ : (d->source->flags & PA_SOURCE_DECIBEL_VOLUME);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_convertible_to_decibel_volume);
+}
+
+static void handle_get_base_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_uint32_t base_volume;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ base_volume = (d->type == DEVICE_TYPE_SINK) ? d->sink->base_volume : d->source->base_volume;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &base_volume);
+}
+
+static void handle_get_volume_steps(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_uint32_t volume_steps;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ volume_steps = (d->type == DEVICE_TYPE_SINK) ? d->sink->n_volume_steps : d->source->n_volume_steps;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &volume_steps);
+}
+
+static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &d->mute);
+}
+
+static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_bool_t mute = FALSE;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(d);
+
+ dbus_message_iter_get_basic(iter, &mute);
+
+ if (d->type == DEVICE_TYPE_SINK)
+ pa_sink_set_mute(d->sink, mute, TRUE);
+ else
+ pa_source_set_mute(d->source, mute, TRUE);
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_has_hardware_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_bool_t has_hardware_volume = FALSE;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ has_hardware_volume = (d->type == DEVICE_TYPE_SINK)
+ ? (d->sink->flags & PA_SINK_HW_VOLUME_CTRL)
+ : (d->source->flags & PA_SOURCE_HW_VOLUME_CTRL);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_hardware_volume);
+}
+
+static void handle_get_has_hardware_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_bool_t has_hardware_mute = FALSE;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ has_hardware_mute = (d->type == DEVICE_TYPE_SINK)
+ ? (d->sink->flags & PA_SINK_HW_MUTE_CTRL)
+ : (d->source->flags & PA_SOURCE_HW_MUTE_CTRL);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_hardware_mute);
+}
+
+static void handle_get_configured_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_uint64_t configured_latency = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ configured_latency = (d->type == DEVICE_TYPE_SINK)
+ ? pa_sink_get_requested_latency(d->sink)
+ : pa_source_get_requested_latency(d->source);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &configured_latency);
+}
+
+static void handle_get_has_dynamic_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_bool_t has_dynamic_latency = FALSE;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ has_dynamic_latency = (d->type == DEVICE_TYPE_SINK)
+ ? (d->sink->flags & PA_SINK_DYNAMIC_LATENCY)
+ : (d->source->flags & PA_SOURCE_DYNAMIC_LATENCY);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_dynamic_latency);
+}
+
+static void handle_get_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_uint64_t latency = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ if (d->type == DEVICE_TYPE_SINK && !(d->sink->flags & PA_SINK_LATENCY))
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Sink %s doesn't support latency querying.", d->sink->name);
+ else if (d->type == DEVICE_TYPE_SOURCE && !(d->source->flags & PA_SOURCE_LATENCY))
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Source %s doesn't support latency querying.", d->source->name);
+ return;
+
+ latency = (d->type == DEVICE_TYPE_SINK) ? pa_sink_get_latency(d->sink) : pa_source_get_latency(d->source);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &latency);
+}
+
+static void handle_get_is_hardware_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_bool_t is_hardware_device = FALSE;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ is_hardware_device = (d->type == DEVICE_TYPE_SINK)
+ ? (d->sink->flags & PA_SINK_HARDWARE)
+ : (d->source->flags & PA_SOURCE_HARDWARE);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_hardware_device);
+}
+
+static void handle_get_is_network_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_bool_t is_network_device = FALSE;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ is_network_device = (d->type == DEVICE_TYPE_SINK)
+ ? (d->sink->flags & PA_SINK_NETWORK)
+ : (d->source->flags & PA_SOURCE_NETWORK);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_network_device);
+}
+
+static void handle_get_state(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_uint32_t state;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ state = (d->type == DEVICE_TYPE_SINK) ? d->sink_state : d->source_state;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &state);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_ports(pa_dbusiface_device *d, unsigned *n) {
+ const char **ports;
+ unsigned i = 0;
+ void *state = NULL;
+ pa_dbusiface_device_port *port = NULL;
+
+ pa_assert(d);
+ pa_assert(n);
+
+ *n = pa_hashmap_size(d->ports);
+
+ if (*n == 0)
+ return NULL;
+
+ ports = pa_xnew(const char *, *n);
+
+ PA_HASHMAP_FOREACH(port, d->ports, state)
+ ports[i++] = pa_dbusiface_device_port_get_path(port);
+
+ return ports;
+}
+
+static void handle_get_ports(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ const char **ports = NULL;
+ unsigned n_ports = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ ports = get_ports(d, &n_ports);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, ports, n_ports);
+
+ pa_xfree(ports);
+}
+
+static void handle_get_active_port(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ const char *active_port;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ if (!d->active_port) {
+ pa_assert(pa_hashmap_isempty(d->ports));
+
+ if (d->type == DEVICE_TYPE_SINK)
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "The sink %s has no ports, and therefore there's no active port either.", d->sink->name);
+ else
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "The source %s has no ports, and therefore there's no active port either.", d->source->name);
+ return;
+ }
+
+ active_port = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name));
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &active_port);
+}
+
+static void handle_set_active_port(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ const char *new_active_path;
+ pa_dbusiface_device_port *new_active;
+ int r;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(d);
+
+ if (!d->active_port) {
+ pa_assert(pa_hashmap_isempty(d->ports));
+
+ if (d->type == DEVICE_TYPE_SINK)
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "The sink %s has no ports, and therefore there's no active port either.", d->sink->name);
+ else
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "The source %s has no ports, and therefore there's no active port either.", d->source->name);
+ return;
+ }
+
+ dbus_message_iter_get_basic(iter, &new_active_path);
+
+ if (!(new_active = pa_hashmap_get(d->ports, new_active_path))) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such port: %s", new_active_path);
+ return;
+ }
+
+ if (d->type == DEVICE_TYPE_SINK) {
+ if ((r = pa_sink_set_port(d->sink, pa_dbusiface_device_port_get_name(new_active), TRUE)) < 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+ "Internal error in PulseAudio: pa_sink_set_port() failed with error code %i.", r);
+ return;
+ }
+ } else {
+ if ((r = pa_source_set_port(d->source, pa_dbusiface_device_port_get_name(new_active), TRUE)) < 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+ "Internal error in PulseAudio: pa_source_set_port() failed with error code %i.", r);
+ return;
+ }
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ pa_dbus_send_proplist_variant_reply(conn, msg, d->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ dbus_uint32_t idx = 0;
+ const char *name = NULL;
+ const char *driver = NULL;
+ pa_module *owner_module = NULL;
+ const char *owner_module_path = NULL;
+ pa_card *card = NULL;
+ const char *card_path = NULL;
+ dbus_uint32_t sample_format = 0;
+ dbus_uint32_t sample_rate = 0;
+ pa_channel_map *channel_map = NULL;
+ dbus_uint32_t channels[PA_CHANNELS_MAX];
+ dbus_uint32_t volume[PA_CHANNELS_MAX];
+ dbus_bool_t has_flat_volume = FALSE;
+ dbus_bool_t has_convertible_to_decibel_volume = FALSE;
+ dbus_uint32_t base_volume = 0;
+ dbus_uint32_t volume_steps = 0;
+ dbus_bool_t has_hardware_volume = FALSE;
+ dbus_bool_t has_hardware_mute = FALSE;
+ dbus_uint64_t configured_latency = 0;
+ dbus_bool_t has_dynamic_latency = FALSE;
+ dbus_uint64_t latency = 0;
+ dbus_bool_t is_hardware_device = FALSE;
+ dbus_bool_t is_network_device = FALSE;
+ dbus_uint32_t state = 0;
+ const char **ports = NULL;
+ unsigned n_ports = 0;
+ const char *active_port = NULL;
+ unsigned i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ if (d->type == DEVICE_TYPE_SINK) {
+ idx = d->sink->index;
+ name = d->sink->name;
+ driver = d->sink->driver;
+ owner_module = d->sink->module;
+ card = d->sink->card;
+ sample_format = d->sink->sample_spec.format;
+ sample_rate = d->sink->sample_spec.rate;
+ channel_map = &d->sink->channel_map;
+ has_flat_volume = d->sink->flags & PA_SINK_FLAT_VOLUME;
+ has_convertible_to_decibel_volume = d->sink->flags & PA_SINK_DECIBEL_VOLUME;
+ base_volume = d->sink->base_volume;
+ volume_steps = d->sink->n_volume_steps;
+ has_hardware_volume = d->sink->flags & PA_SINK_HW_VOLUME_CTRL;
+ has_hardware_mute = d->sink->flags & PA_SINK_HW_MUTE_CTRL;
+ configured_latency = pa_sink_get_requested_latency(d->sink);
+ has_dynamic_latency = d->sink->flags & PA_SINK_DYNAMIC_LATENCY;
+ latency = pa_sink_get_latency(d->sink);
+ is_hardware_device = d->sink->flags & PA_SINK_HARDWARE;
+ is_network_device = d->sink->flags & PA_SINK_NETWORK;
+ state = pa_sink_get_state(d->sink);
+ } else {
+ idx = d->source->index;
+ name = d->source->name;
+ driver = d->source->driver;
+ owner_module = d->source->module;
+ card = d->source->card;
+ sample_format = d->source->sample_spec.format;
+ sample_rate = d->source->sample_spec.rate;
+ channel_map = &d->source->channel_map;
+ has_flat_volume = FALSE;
+ has_convertible_to_decibel_volume = d->source->flags & PA_SOURCE_DECIBEL_VOLUME;
+ base_volume = d->source->base_volume;
+ volume_steps = d->source->n_volume_steps;
+ has_hardware_volume = d->source->flags & PA_SOURCE_HW_VOLUME_CTRL;
+ has_hardware_mute = d->source->flags & PA_SOURCE_HW_MUTE_CTRL;
+ configured_latency = pa_source_get_requested_latency(d->source);
+ has_dynamic_latency = d->source->flags & PA_SOURCE_DYNAMIC_LATENCY;
+ latency = pa_source_get_latency(d->source);
+ is_hardware_device = d->source->flags & PA_SOURCE_HARDWARE;
+ is_network_device = d->source->flags & PA_SOURCE_NETWORK;
+ state = pa_source_get_state(d->source);
+ }
+ if (owner_module)
+ owner_module_path = pa_dbusiface_core_get_module_path(d->core, owner_module);
+ if (card)
+ card_path = pa_dbusiface_core_get_card_path(d->core, card);
+ for (i = 0; i < channel_map->channels; ++i)
+ channels[i] = channel_map->map[i];
+ for (i = 0; i < d->volume.channels; ++i)
+ volume[i] = d->volume.values[i];
+ ports = get_ports(d, &n_ports);
+ if (d->active_port)
+ active_port = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name));
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &name);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &driver);
+
+ if (owner_module)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module_path);
+
+ if (card)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CARD].property_name, DBUS_TYPE_OBJECT_PATH, &card_path);
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &sample_rate);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, channel_map->channels);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME].property_name, DBUS_TYPE_UINT32, volume, d->volume.channels);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_FLAT_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_flat_volume);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_convertible_to_decibel_volume);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BASE_VOLUME].property_name, DBUS_TYPE_UINT32, &base_volume);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME_STEPS].property_name, DBUS_TYPE_UINT32, &volume_steps);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &d->mute);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_HARDWARE_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_hardware_volume);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_HARDWARE_MUTE].property_name, DBUS_TYPE_BOOLEAN, &has_hardware_mute);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CONFIGURED_LATENCY].property_name, DBUS_TYPE_UINT64, &configured_latency);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY].property_name, DBUS_TYPE_BOOLEAN, &has_dynamic_latency);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_LATENCY].property_name, DBUS_TYPE_UINT64, &latency);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_HARDWARE_DEVICE].property_name, DBUS_TYPE_BOOLEAN, &is_hardware_device);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_NETWORK_DEVICE].property_name, DBUS_TYPE_BOOLEAN, &is_network_device);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_STATE].property_name, DBUS_TYPE_UINT32, &state);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PORTS].property_name, DBUS_TYPE_OBJECT_PATH, ports, n_ports);
+
+ if (active_port)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACTIVE_PORT].property_name, DBUS_TYPE_OBJECT_PATH, &active_port);
+
+ pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, d->proplist);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+
+ pa_xfree(ports);
+}
+
+static void handle_suspend(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ dbus_bool_t suspend = FALSE;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &suspend, DBUS_TYPE_INVALID));
+
+ if ((d->type == DEVICE_TYPE_SINK) && (pa_sink_suspend(d->sink, suspend, PA_SUSPEND_USER) < 0)) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Internal error in PulseAudio: pa_sink_suspend() failed.");
+ return;
+ } else if ((d->type == DEVICE_TYPE_SOURCE) && (pa_source_suspend(d->source, suspend, PA_SUSPEND_USER) < 0)) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Internal error in PulseAudio: pa_source_suspend() failed.");
+ return;
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_port_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ const char *port_name = NULL;
+ pa_dbusiface_device_port *port = NULL;
+ const char *port_path = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+
+ pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &port_name, DBUS_TYPE_INVALID));
+
+ if (!(port = pa_hashmap_get(d->ports, port_name))) {
+ if (d->type == DEVICE_TYPE_SINK)
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND,
+ "%s: No such port on sink %s.", port_name, d->sink->name);
+ else
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND,
+ "%s: No such port on source %s.", port_name, d->source->name);
+ return;
+ }
+
+ port_path = pa_dbusiface_device_port_get_path(port);
+
+ pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &port_path);
+}
+
+static void handle_sink_get_monitor_source(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ const char *monitor_source = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+ pa_assert(d->type == DEVICE_TYPE_SINK);
+
+ monitor_source = pa_dbusiface_core_get_source_path(d->core, d->sink->monitor_source);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &monitor_source);
+}
+
+static void handle_sink_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ const char *monitor_source = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+ pa_assert(d->type == DEVICE_TYPE_SINK);
+
+ monitor_source = pa_dbusiface_core_get_source_path(d->core, d->sink->monitor_source);
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[SINK_PROPERTY_HANDLER_MONITOR_SOURCE].property_name, DBUS_TYPE_OBJECT_PATH, &monitor_source);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+}
+
+static void handle_source_get_monitor_of_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ const char *monitor_of_sink = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+ pa_assert(d->type == DEVICE_TYPE_SOURCE);
+
+ if (!d->source->monitor_of) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Source %s is not a monitor source.", d->source->name);
+ return;
+ }
+
+ monitor_of_sink = pa_dbusiface_core_get_sink_path(d->core, d->source->monitor_of);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &monitor_of_sink);
+}
+
+static void handle_source_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ const char *monitor_of_sink = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(d);
+ pa_assert(d->type == DEVICE_TYPE_SOURCE);
+
+ if (d->source->monitor_of)
+ monitor_of_sink = pa_dbusiface_core_get_sink_path(d->core, d->source->monitor_of);
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ if (monitor_of_sink)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK].property_name, DBUS_TYPE_OBJECT_PATH, &monitor_of_sink);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+}
+
+static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ pa_dbusiface_device *d = userdata;
+ DBusMessage *signal = NULL;
+ const pa_cvolume *new_volume = NULL;
+ pa_bool_t new_mute = FALSE;
+ pa_sink_state_t new_sink_state = 0;
+ pa_source_state_t new_source_state = 0;
+ pa_device_port *new_active_port = NULL;
+ pa_proplist *new_proplist = NULL;
+ unsigned i = 0;
+
+ pa_assert(c);
+ pa_assert(d);
+
+ if ((d->type == DEVICE_TYPE_SINK && idx != d->sink->index) || (d->type == DEVICE_TYPE_SOURCE && idx != d->source->index))
+ return;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+ return;
+
+ pa_assert(((d->type == DEVICE_TYPE_SINK)
+ && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK))
+ || ((d->type == DEVICE_TYPE_SOURCE)
+ && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE)));
+
+ new_volume = (d->type == DEVICE_TYPE_SINK)
+ ? pa_sink_get_volume(d->sink, FALSE)
+ : pa_source_get_volume(d->source, FALSE);
+
+ if (!pa_cvolume_equal(&d->volume, new_volume)) {
+ dbus_uint32_t volume[PA_CHANNELS_MAX];
+ dbus_uint32_t *volume_ptr = volume;
+
+ d->volume = *new_volume;
+
+ for (i = 0; i < d->volume.channels; ++i)
+ volume[i] = d->volume.values[i];
+
+ pa_assert_se(signal = dbus_message_new_signal(d->path,
+ PA_DBUSIFACE_DEVICE_INTERFACE,
+ signals[SIGNAL_VOLUME_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &volume_ptr, d->volume.channels,
+ DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(d->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+
+ new_mute = (d->type == DEVICE_TYPE_SINK) ? pa_sink_get_mute(d->sink, FALSE) : pa_source_get_mute(d->source, FALSE);
+
+ if (d->mute != new_mute) {
+ d->mute = new_mute;
+
+ pa_assert_se(signal = dbus_message_new_signal(d->path,
+ PA_DBUSIFACE_DEVICE_INTERFACE,
+ signals[SIGNAL_MUTE_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_BOOLEAN, &d->mute, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(d->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+
+ if (d->type == DEVICE_TYPE_SINK)
+ new_sink_state = pa_sink_get_state(d->sink);
+ else
+ new_source_state = pa_source_get_state(d->source);
+
+ if ((d->type == DEVICE_TYPE_SINK && d->sink_state != new_sink_state)
+ || (d->type == DEVICE_TYPE_SOURCE && d->source_state != new_source_state)) {
+ dbus_uint32_t state = 0;
+
+ if (d->type == DEVICE_TYPE_SINK)
+ d->sink_state = new_sink_state;
+ else
+ d->source_state = new_source_state;
+
+ state = (d->type == DEVICE_TYPE_SINK) ? d->sink_state : d->source_state;
+
+ pa_assert_se(signal = dbus_message_new_signal(d->path,
+ PA_DBUSIFACE_DEVICE_INTERFACE,
+ signals[SIGNAL_STATE_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_UINT32, &state, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(d->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+
+ new_active_port = (d->type == DEVICE_TYPE_SINK) ? d->sink->active_port : d->source->active_port;
+
+ if (d->active_port != new_active_port) {
+ const char *object_path = NULL;
+
+ d->active_port = new_active_port;
+ object_path = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name));
+
+ pa_assert_se(signal = dbus_message_new_signal(d->path,
+ PA_DBUSIFACE_DEVICE_INTERFACE,
+ signals[SIGNAL_ACTIVE_PORT_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(d->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+
+ new_proplist = (d->type == DEVICE_TYPE_SINK) ? d->sink->proplist : d->source->proplist;
+
+ if (!pa_proplist_equal(d->proplist, new_proplist)) {
+ DBusMessageIter msg_iter;
+
+ pa_proplist_update(d->proplist, PA_UPDATE_SET, new_proplist);
+
+ pa_assert_se(signal = dbus_message_new_signal(d->path,
+ PA_DBUSIFACE_DEVICE_INTERFACE,
+ signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+ dbus_message_iter_init_append(signal, &msg_iter);
+ pa_dbus_append_proplist(&msg_iter, d->proplist);
+
+ pa_dbus_protocol_send_signal(d->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+}
+
+pa_dbusiface_device *pa_dbusiface_device_new_sink(pa_dbusiface_core *core, pa_sink *sink) {
+ pa_dbusiface_device *d = NULL;
+
+ pa_assert(core);
+ pa_assert(sink);
+
+ d = pa_xnew0(pa_dbusiface_device, 1);
+ d->core = core;
+ d->sink = pa_sink_ref(sink);
+ d->type = DEVICE_TYPE_SINK;
+ d->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, SINK_OBJECT_NAME, sink->index);
+ d->volume = *pa_sink_get_volume(sink, FALSE);
+ d->mute = pa_sink_get_mute(sink, FALSE);
+ d->sink_state = pa_sink_get_state(sink);
+ d->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ d->next_port_index = 0;
+ d->active_port = NULL;
+ d->proplist = pa_proplist_copy(sink->proplist);
+ d->dbus_protocol = pa_dbus_protocol_get(sink->core);
+ d->subscription = pa_subscription_new(sink->core, PA_SUBSCRIPTION_MASK_SINK, subscription_cb, d);
+
+ if (sink->ports) {
+ pa_device_port *port;
+ void *state = NULL;
+
+ PA_HASHMAP_FOREACH(port, sink->ports, state) {
+ pa_dbusiface_device_port *p = pa_dbusiface_device_port_new(d, sink->core, port, d->next_port_index++);
+ pa_hashmap_put(d->ports, pa_dbusiface_device_port_get_name(p), p);
+ }
+ pa_assert_se(d->active_port = sink->active_port);
+ }
+
+ pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &device_interface_info, d) >= 0);
+ pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &sink_interface_info, d) >= 0);
+
+ return d;
+}
+
+pa_dbusiface_device *pa_dbusiface_device_new_source(pa_dbusiface_core *core, pa_source *source) {
+ pa_dbusiface_device *d = NULL;
+
+ pa_assert(core);
+ pa_assert(source);
+
+ d = pa_xnew0(pa_dbusiface_device, 1);
+ d->core = core;
+ d->source = pa_source_ref(source);
+ d->type = DEVICE_TYPE_SOURCE;
+ d->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, SOURCE_OBJECT_NAME, source->index);
+ d->volume = *pa_source_get_volume(source, FALSE);
+ d->mute = pa_source_get_mute(source, FALSE);
+ d->source_state = pa_source_get_state(source);
+ d->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ d->next_port_index = 0;
+ d->active_port = NULL;
+ d->proplist = pa_proplist_copy(source->proplist);
+ d->dbus_protocol = pa_dbus_protocol_get(source->core);
+ d->subscription = pa_subscription_new(source->core, PA_SUBSCRIPTION_MASK_SOURCE, subscription_cb, d);
+
+ if (source->ports) {
+ pa_device_port *port;
+ void *state = NULL;
+
+ PA_HASHMAP_FOREACH(port, source->ports, state) {
+ pa_dbusiface_device_port *p = pa_dbusiface_device_port_new(d, source->core, port, d->next_port_index++);
+ pa_hashmap_put(d->ports, pa_dbusiface_device_port_get_name(p), p);
+ }
+ pa_assert_se(d->active_port = source->active_port);
+ }
+
+ pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &device_interface_info, d) >= 0);
+ pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &source_interface_info, d) >= 0);
+
+ return d;
+}
+
+static void port_free_cb(void *p, void *userdata) {
+ pa_dbusiface_device_port *port = p;
+
+ pa_assert(port);
+
+ pa_dbusiface_device_port_free(port);
+}
+
+void pa_dbusiface_device_free(pa_dbusiface_device *d) {
+ pa_assert(d);
+
+ pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, device_interface_info.name) >= 0);
+
+ if (d->type == DEVICE_TYPE_SINK) {
+ pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, sink_interface_info.name) >= 0);
+ pa_sink_unref(d->sink);
+
+ } else {
+ pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, source_interface_info.name) >= 0);
+ pa_source_unref(d->source);
+ }
+ pa_hashmap_free(d->ports, port_free_cb, NULL);
+ pa_proplist_free(d->proplist);
+ pa_dbus_protocol_unref(d->dbus_protocol);
+ pa_subscription_free(d->subscription);
+
+ pa_xfree(d->path);
+ pa_xfree(d);
+}
+
+const char *pa_dbusiface_device_get_path(pa_dbusiface_device *d) {
+ pa_assert(d);
+
+ return d->path;
+}
+
+pa_sink *pa_dbusiface_device_get_sink(pa_dbusiface_device *d) {
+ pa_assert(d);
+ pa_assert(d->type == DEVICE_TYPE_SINK);
+
+ return d->sink;
+}
+
+pa_source *pa_dbusiface_device_get_source(pa_dbusiface_device *d) {
+ pa_assert(d);
+ pa_assert(d->type == DEVICE_TYPE_SOURCE);
+
+ return d->source;
+}
diff --git a/src/modules/dbus/iface-device.h b/src/modules/dbus/iface-device.h
new file mode 100644
index 00000000..62e05e9a
--- /dev/null
+++ b/src/modules/dbus/iface-device.h
@@ -0,0 +1,53 @@
+#ifndef foodbusifacedevicehfoo
+#define foodbusifacedevicehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ 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.
+***/
+
+/* This object implements the D-Bus interfaces org.PulseAudio.Core1.Device,
+ * org.PulseAudio.Core1.Sink and org.PulseAudio.Core1.Source.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the interface
+ * documentation.
+ */
+
+#include <pulsecore/protocol-dbus.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_DEVICE_INTERFACE PA_DBUS_CORE_INTERFACE ".Device"
+#define PA_DBUSIFACE_SINK_INTERFACE PA_DBUS_CORE_INTERFACE ".Sink"
+#define PA_DBUSIFACE_SOURCE_INTERFACE PA_DBUS_CORE_INTERFACE ".Source"
+
+typedef struct pa_dbusiface_device pa_dbusiface_device;
+
+pa_dbusiface_device *pa_dbusiface_device_new_sink(pa_dbusiface_core *core, pa_sink *sink);
+pa_dbusiface_device *pa_dbusiface_device_new_source(pa_dbusiface_core *core, pa_source *source);
+void pa_dbusiface_device_free(pa_dbusiface_device *d);
+
+const char *pa_dbusiface_device_get_path(pa_dbusiface_device *d);
+
+pa_sink *pa_dbusiface_device_get_sink(pa_dbusiface_device *d);
+pa_source *pa_dbusiface_device_get_source(pa_dbusiface_device *d);
+
+#endif
diff --git a/src/modules/dbus/iface-memstats.c b/src/modules/dbus/iface-memstats.c
new file mode 100644
index 00000000..73a84be8
--- /dev/null
+++ b/src/modules/dbus/iface-memstats.c
@@ -0,0 +1,231 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core-scache.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-memstats.h"
+
+#define OBJECT_NAME "memstats"
+
+static void handle_get_current_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_current_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_accumulated_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_accumulated_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_cache_size(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+struct pa_dbusiface_memstats {
+ pa_core *core;
+ char *path;
+ pa_dbus_protocol *dbus_protocol;
+};
+
+enum property_handler_index {
+ PROPERTY_HANDLER_CURRENT_MEMBLOCKS,
+ PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE,
+ PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS,
+ PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE,
+ PROPERTY_HANDLER_SAMPLE_CACHE_SIZE,
+ PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+ [PROPERTY_HANDLER_CURRENT_MEMBLOCKS] = { .property_name = "CurrentMemblocks", .type = "u", .get_cb = handle_get_current_memblocks, .set_cb = NULL },
+ [PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE] = { .property_name = "CurrentMemblocksSize", .type = "u", .get_cb = handle_get_current_memblocks_size, .set_cb = NULL },
+ [PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS] = { .property_name = "AccumulatedMemblocks", .type = "u", .get_cb = handle_get_accumulated_memblocks, .set_cb = NULL },
+ [PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE] = { .property_name = "AccumulatedMemblocksSize", .type = "u", .get_cb = handle_get_accumulated_memblocks_size, .set_cb = NULL },
+ [PROPERTY_HANDLER_SAMPLE_CACHE_SIZE] = { .property_name = "SampleCacheSize", .type = "u", .get_cb = handle_get_sample_cache_size, .set_cb = NULL }
+};
+
+static pa_dbus_interface_info memstats_interface_info = {
+ .name = PA_DBUSIFACE_MEMSTATS_INTERFACE,
+ .method_handlers = NULL,
+ .n_method_handlers = 0,
+ .property_handlers = property_handlers,
+ .n_property_handlers = PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_get_all,
+ .signals = NULL,
+ .n_signals = 0
+};
+
+static void handle_get_current_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_memstats *m = userdata;
+ const pa_mempool_stat *stat;
+ dbus_uint32_t current_memblocks;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ stat = pa_mempool_get_stat(m->core->mempool);
+
+ current_memblocks = pa_atomic_load(&stat->n_allocated);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &current_memblocks);
+}
+
+static void handle_get_current_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_memstats *m = userdata;
+ const pa_mempool_stat *stat;
+ dbus_uint32_t current_memblocks_size;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ stat = pa_mempool_get_stat(m->core->mempool);
+
+ current_memblocks_size = pa_atomic_load(&stat->allocated_size);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &current_memblocks_size);
+}
+
+static void handle_get_accumulated_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_memstats *m = userdata;
+ const pa_mempool_stat *stat;
+ dbus_uint32_t accumulated_memblocks;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ stat = pa_mempool_get_stat(m->core->mempool);
+
+ accumulated_memblocks = pa_atomic_load(&stat->n_accumulated);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &accumulated_memblocks);
+}
+
+static void handle_get_accumulated_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_memstats *m = userdata;
+ const pa_mempool_stat *stat;
+ dbus_uint32_t accumulated_memblocks_size;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ stat = pa_mempool_get_stat(m->core->mempool);
+
+ accumulated_memblocks_size = pa_atomic_load(&stat->accumulated_size);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &accumulated_memblocks_size);
+}
+
+static void handle_get_sample_cache_size(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_memstats *m = userdata;
+ dbus_uint32_t sample_cache_size;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ sample_cache_size = pa_scache_total_size(m->core);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_cache_size);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_memstats *m = userdata;
+ const pa_mempool_stat *stat;
+ dbus_uint32_t current_memblocks;
+ dbus_uint32_t current_memblocks_size;
+ dbus_uint32_t accumulated_memblocks;
+ dbus_uint32_t accumulated_memblocks_size;
+ dbus_uint32_t sample_cache_size;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ stat = pa_mempool_get_stat(m->core->mempool);
+
+ current_memblocks = pa_atomic_load(&stat->n_allocated);
+ current_memblocks_size = pa_atomic_load(&stat->allocated_size);
+ accumulated_memblocks = pa_atomic_load(&stat->n_accumulated);
+ accumulated_memblocks_size = pa_atomic_load(&stat->accumulated_size);
+ sample_cache_size = pa_scache_total_size(m->core);
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CURRENT_MEMBLOCKS].property_name, DBUS_TYPE_UINT32, &current_memblocks);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE].property_name, DBUS_TYPE_UINT32, &current_memblocks_size);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS].property_name, DBUS_TYPE_UINT32, &accumulated_memblocks);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE].property_name, DBUS_TYPE_UINT32, &accumulated_memblocks_size);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_CACHE_SIZE].property_name, DBUS_TYPE_UINT32, &sample_cache_size);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+}
+
+pa_dbusiface_memstats *pa_dbusiface_memstats_new(pa_dbusiface_core *dbus_core, pa_core *core) {
+ pa_dbusiface_memstats *m;
+
+ pa_assert(dbus_core);
+ pa_assert(core);
+
+ m = pa_xnew(pa_dbusiface_memstats, 1);
+ m->core = pa_core_ref(core);
+ m->path = pa_sprintf_malloc("%s/%s", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME);
+ m->dbus_protocol = pa_dbus_protocol_get(core);
+
+ pa_assert_se(pa_dbus_protocol_add_interface(m->dbus_protocol, m->path, &memstats_interface_info, m) >= 0);
+
+ return m;
+}
+
+void pa_dbusiface_memstats_free(pa_dbusiface_memstats *m) {
+ pa_assert(m);
+
+ pa_assert_se(pa_dbus_protocol_remove_interface(m->dbus_protocol, m->path, memstats_interface_info.name) >= 0);
+
+ pa_xfree(m->path);
+
+ pa_dbus_protocol_unref(m->dbus_protocol);
+ pa_core_unref(m->core);
+
+ pa_xfree(m);
+}
+
+const char *pa_dbusiface_memstats_get_path(pa_dbusiface_memstats *m) {
+ pa_assert(m);
+
+ return m->path;
+}
diff --git a/src/modules/dbus/iface-memstats.h b/src/modules/dbus/iface-memstats.h
new file mode 100644
index 00000000..0820e8fe
--- /dev/null
+++ b/src/modules/dbus/iface-memstats.h
@@ -0,0 +1,45 @@
+#ifndef foodbusifacememstatshfoo
+#define foodbusifacememstatshfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ 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.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Memstats.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Memstats interface
+ * documentation.
+ */
+
+#include <pulsecore/core.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_MEMSTATS_INTERFACE PA_DBUS_CORE_INTERFACE ".Memstats"
+
+typedef struct pa_dbusiface_memstats pa_dbusiface_memstats;
+
+pa_dbusiface_memstats *pa_dbusiface_memstats_new(pa_dbusiface_core *dbus_core, pa_core *core);
+void pa_dbusiface_memstats_free(pa_dbusiface_memstats *m);
+
+const char *pa_dbusiface_memstats_get_path(pa_dbusiface_memstats *m);
+
+#endif
diff --git a/src/modules/dbus/iface-module.c b/src/modules/dbus/iface-module.c
new file mode 100644
index 00000000..e8aea50f
--- /dev/null
+++ b/src/modules/dbus/iface-module.c
@@ -0,0 +1,336 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-module.h"
+
+#define OBJECT_NAME "module"
+
+struct pa_dbusiface_module {
+ pa_module *module;
+ char *path;
+ pa_proplist *proplist;
+
+ pa_dbus_protocol *dbus_protocol;
+ pa_subscription *subscription;
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_arguments(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_usage_counter(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_unload(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum property_handler_index {
+ PROPERTY_HANDLER_INDEX,
+ PROPERTY_HANDLER_NAME,
+ PROPERTY_HANDLER_ARGUMENTS,
+ PROPERTY_HANDLER_USAGE_COUNTER,
+ PROPERTY_HANDLER_PROPERTY_LIST,
+ PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+ [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL },
+ [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL },
+ [PROPERTY_HANDLER_ARGUMENTS] = { .property_name = "Arguments", .type = "a{ss}", .get_cb = handle_get_arguments, .set_cb = NULL },
+ [PROPERTY_HANDLER_USAGE_COUNTER] = { .property_name = "UsageCounter", .type = "u", .get_cb = handle_get_usage_counter, .set_cb = NULL },
+ [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL }
+};
+
+enum method_handler_index {
+ METHOD_HANDLER_UNLOAD,
+ METHOD_HANDLER_MAX
+};
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+ [METHOD_HANDLER_UNLOAD] = {
+ .method_name = "Unload",
+ .arguments = NULL,
+ .n_arguments = 0,
+ .receive_cb = handle_unload }
+};
+
+enum signal_index {
+ SIGNAL_PROPERTY_LIST_UPDATED,
+ SIGNAL_MAX
+};
+
+static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+ [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info module_interface_info = {
+ .name = PA_DBUSIFACE_MODULE_INTERFACE,
+ .method_handlers = method_handlers,
+ .n_method_handlers = METHOD_HANDLER_MAX,
+ .property_handlers = property_handlers,
+ .n_property_handlers = PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_get_all,
+ .signals = signals,
+ .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_module *m = userdata;
+ dbus_uint32_t idx = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ idx = m->module->index;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_module *m = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &m->module->name);
+}
+
+static void append_modargs_variant(DBusMessageIter *iter, pa_dbusiface_module *m) {
+ pa_modargs *ma = NULL;
+ DBusMessageIter variant_iter;
+ DBusMessageIter dict_iter;
+ DBusMessageIter dict_entry_iter;
+ void *state = NULL;
+ const char *key = NULL;
+ const char *value = NULL;
+
+ pa_assert(iter);
+ pa_assert(m);
+
+ pa_assert_se(ma = pa_modargs_new(m->module->argument, NULL));
+
+ pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{ss}", &variant_iter));
+ pa_assert_se(dbus_message_iter_open_container(&variant_iter, DBUS_TYPE_ARRAY, "{ss}", &dict_iter));
+
+ for (state = NULL, key = pa_modargs_iterate(ma, &state); key; key = pa_modargs_iterate(ma, &state)) {
+ pa_assert_se(value = pa_modargs_get_value(ma, key, NULL));
+
+ pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+
+ pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key));
+ pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &value));
+
+ pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter));
+ }
+
+ pa_assert_se(dbus_message_iter_close_container(&variant_iter, &dict_iter));
+ pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter));
+
+ pa_modargs_free(ma);
+}
+
+static void handle_get_arguments(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_module *m = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ pa_assert_se(reply = dbus_message_new_method_return(msg));
+ dbus_message_iter_init_append(reply, &msg_iter);
+ append_modargs_variant(&msg_iter, m);
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+ dbus_message_unref(reply);
+}
+
+static void handle_get_usage_counter(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_module *m = userdata;
+ int real_counter_value = -1;
+ dbus_uint32_t usage_counter = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ if (!m->module->get_n_used || (real_counter_value = m->module->get_n_used(m->module)) < 0) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Module %u (%s) doesn't have a usage counter.", m->module->index, m->module->name);
+ return;
+ }
+
+ usage_counter = real_counter_value;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &usage_counter);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_module *m = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ pa_dbus_send_proplist_variant_reply(conn, msg, m->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_module *m = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ DBusMessageIter dict_entry_iter;
+ dbus_uint32_t idx = 0;
+ int real_counter_value = -1;
+ dbus_uint32_t usage_counter = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ idx = m->module->index;
+ if (m->module->get_n_used && (real_counter_value = m->module->get_n_used(m->module)) >= 0)
+ usage_counter = real_counter_value;
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &m->module->name);
+
+ pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+ pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &property_handlers[PROPERTY_HANDLER_ARGUMENTS].property_name));
+ append_modargs_variant(&dict_entry_iter, m);
+ pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter));
+
+ if (real_counter_value >= 0)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ARGUMENTS].property_name, DBUS_TYPE_UINT32, &usage_counter);
+
+ pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, m->proplist);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+}
+
+static void handle_unload(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_module *m = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(m);
+
+ if (m->module->core->disallow_module_loading) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow module unloading.");
+ return;
+ }
+
+ pa_module_unload_request(m->module, FALSE);
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ pa_dbusiface_module *m = userdata;
+ DBusMessage *signal = NULL;
+
+ pa_assert(core);
+ pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_MODULE);
+ pa_assert(m);
+
+ /* We can't use idx != m->module->index, because the m->module pointer may
+ * be stale at this point. */
+ if (pa_idxset_get_by_index(core->modules, idx) != m->module)
+ return;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+ return;
+
+ if (!pa_proplist_equal(m->proplist, m->module->proplist)) {
+ DBusMessageIter msg_iter;
+
+ pa_proplist_update(m->proplist, PA_UPDATE_SET, m->module->proplist);
+
+ pa_assert_se(signal = dbus_message_new_signal(m->path,
+ PA_DBUSIFACE_MODULE_INTERFACE,
+ signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+ dbus_message_iter_init_append(signal, &msg_iter);
+ pa_dbus_append_proplist(&msg_iter, m->proplist);
+
+ pa_dbus_protocol_send_signal(m->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+}
+
+pa_dbusiface_module *pa_dbusiface_module_new(pa_module *module) {
+ pa_dbusiface_module *m;
+
+ pa_assert(module);
+
+ m = pa_xnew0(pa_dbusiface_module, 1);
+ m->module = module;
+ m->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, module->index);
+ m->proplist = pa_proplist_copy(module->proplist);
+ m->dbus_protocol = pa_dbus_protocol_get(module->core);
+ m->subscription = pa_subscription_new(module->core, PA_SUBSCRIPTION_MASK_MODULE, subscription_cb, m);
+
+ pa_assert_se(pa_dbus_protocol_add_interface(m->dbus_protocol, m->path, &module_interface_info, m) >= 0);
+
+ return m;
+}
+
+void pa_dbusiface_module_free(pa_dbusiface_module *m) {
+ pa_assert(m);
+
+ pa_assert_se(pa_dbus_protocol_remove_interface(m->dbus_protocol, m->path, module_interface_info.name) >= 0);
+
+ pa_proplist_free(m->proplist);
+ pa_dbus_protocol_unref(m->dbus_protocol);
+ pa_subscription_free(m->subscription);
+
+ pa_xfree(m->path);
+ pa_xfree(m);
+}
+
+const char *pa_dbusiface_module_get_path(pa_dbusiface_module *m) {
+ pa_assert(m);
+
+ return m->path;
+}
diff --git a/src/modules/dbus/iface-module.h b/src/modules/dbus/iface-module.h
new file mode 100644
index 00000000..68ca1de5
--- /dev/null
+++ b/src/modules/dbus/iface-module.h
@@ -0,0 +1,45 @@
+#ifndef foodbusifacemodulehfoo
+#define foodbusifacemodulehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ 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.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Module.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Module interface
+ * documentation.
+ */
+
+#include <pulsecore/module.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_MODULE_INTERFACE PA_DBUS_CORE_INTERFACE ".Module"
+
+typedef struct pa_dbusiface_module pa_dbusiface_module;
+
+pa_dbusiface_module *pa_dbusiface_module_new(pa_module *module);
+void pa_dbusiface_module_free(pa_dbusiface_module *m);
+
+const char *pa_dbusiface_module_get_path(pa_dbusiface_module *m);
+
+#endif
diff --git a/src/modules/dbus/iface-sample.c b/src/modules/dbus/iface-sample.c
new file mode 100644
index 00000000..b0542a60
--- /dev/null
+++ b/src/modules/dbus/iface-sample.c
@@ -0,0 +1,519 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-sample.h"
+
+#define OBJECT_NAME "sample"
+
+struct pa_dbusiface_sample {
+ pa_dbusiface_core *core;
+
+ pa_scache_entry *sample;
+ char *path;
+ pa_proplist *proplist;
+
+ pa_dbus_protocol *dbus_protocol;
+ pa_subscription *subscription;
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_default_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_duration(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_bytes(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_play(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_play_to_sink(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_remove(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum property_handler_index {
+ PROPERTY_HANDLER_INDEX,
+ PROPERTY_HANDLER_NAME,
+ PROPERTY_HANDLER_SAMPLE_FORMAT,
+ PROPERTY_HANDLER_SAMPLE_RATE,
+ PROPERTY_HANDLER_CHANNELS,
+ PROPERTY_HANDLER_DEFAULT_VOLUME,
+ PROPERTY_HANDLER_DURATION,
+ PROPERTY_HANDLER_BYTES,
+ PROPERTY_HANDLER_PROPERTY_LIST,
+ PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+ [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL },
+ [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL },
+ [PROPERTY_HANDLER_SAMPLE_FORMAT] = { .property_name = "SampleFormat", .type = "u", .get_cb = handle_get_sample_format, .set_cb = NULL },
+ [PROPERTY_HANDLER_SAMPLE_RATE] = { .property_name = "SampleRate", .type = "u", .get_cb = handle_get_sample_rate, .set_cb = NULL },
+ [PROPERTY_HANDLER_CHANNELS] = { .property_name = "Channels", .type = "au", .get_cb = handle_get_channels, .set_cb = NULL },
+ [PROPERTY_HANDLER_DEFAULT_VOLUME] = { .property_name = "DefaultVolume", .type = "au", .get_cb = handle_get_default_volume, .set_cb = NULL },
+ [PROPERTY_HANDLER_DURATION] = { .property_name = "Duration", .type = "t", .get_cb = handle_get_duration, .set_cb = NULL },
+ [PROPERTY_HANDLER_BYTES] = { .property_name = "Bytes", .type = "u", .get_cb = handle_get_bytes, .set_cb = NULL },
+ [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL }
+};
+
+enum method_handler_index {
+ METHOD_HANDLER_PLAY,
+ METHOD_HANDLER_PLAY_TO_SINK,
+ METHOD_HANDLER_REMOVE,
+ METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info play_args[] = { { "volume", "u", "in" }, { "property_list", "a{say}", "in" } };
+static pa_dbus_arg_info play_to_sink_args[] = { { "sink", "o", "in" },
+ { "volume", "u", "in" },
+ { "property_list", "a{say}", "in" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+ [METHOD_HANDLER_PLAY] = {
+ .method_name = "Play",
+ .arguments = play_args,
+ .n_arguments = sizeof(play_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_play },
+ [METHOD_HANDLER_PLAY_TO_SINK] = {
+ .method_name = "PlayToSink",
+ .arguments = play_to_sink_args,
+ .n_arguments = sizeof(play_to_sink_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_play_to_sink },
+ [METHOD_HANDLER_REMOVE] = {
+ .method_name = "Remove",
+ .arguments = NULL,
+ .n_arguments = 0,
+ .receive_cb = handle_remove }
+};
+
+enum signal_index {
+ SIGNAL_PROPERTY_LIST_UPDATED,
+ SIGNAL_MAX
+};
+
+static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+ [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info sample_interface_info = {
+ .name = PA_DBUSIFACE_SAMPLE_INTERFACE,
+ .method_handlers = method_handlers,
+ .n_method_handlers = METHOD_HANDLER_MAX,
+ .property_handlers = property_handlers,
+ .n_property_handlers = PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_get_all,
+ .signals = signals,
+ .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+ dbus_uint32_t idx = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ idx = s->sample->index;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &s->sample->name);
+}
+
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+ dbus_uint32_t sample_format = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (!s->sample->memchunk.memblock) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Sample %s isn't loaded into memory yet, so its sample format is unknown.", s->sample->name);
+ return;
+ }
+
+ sample_format = s->sample->sample_spec.format;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format);
+}
+
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+ dbus_uint32_t sample_rate = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (!s->sample->memchunk.memblock) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Sample %s isn't loaded into memory yet, so its sample rate is unknown.", s->sample->name);
+ return;
+ }
+
+ sample_rate = s->sample->sample_spec.rate;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_rate);
+}
+
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+ dbus_uint32_t channels[PA_CHANNELS_MAX];
+ unsigned i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (!s->sample->memchunk.memblock) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Sample %s isn't loaded into memory yet, so its channel map is unknown.", s->sample->name);
+ return;
+ }
+
+ for (i = 0; i < s->sample->channel_map.channels; ++i)
+ channels[i] = s->sample->channel_map.map[i];
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, s->sample->channel_map.channels);
+}
+
+static void handle_get_default_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+ dbus_uint32_t default_volume[PA_CHANNELS_MAX];
+ unsigned i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (!s->sample->volume_is_set) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Sample %s doesn't have default volume stored.", s->sample->name);
+ return;
+ }
+
+ for (i = 0; i < s->sample->volume.channels; ++i)
+ default_volume[i] = s->sample->volume.values[i];
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, default_volume, s->sample->volume.channels);
+}
+
+static void handle_get_duration(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+ dbus_uint64_t duration = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (!s->sample->memchunk.memblock) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Sample %s isn't loaded into memory yet, so its duration is unknown.", s->sample->name);
+ return;
+ }
+
+ duration = pa_bytes_to_usec(s->sample->memchunk.length, &s->sample->sample_spec);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &duration);
+}
+
+static void handle_get_bytes(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+ dbus_uint32_t bytes = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (!s->sample->memchunk.memblock) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Sample %s isn't loaded into memory yet, so its size is unknown.", s->sample->name);
+ return;
+ }
+
+ bytes = s->sample->memchunk.length;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &bytes);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ pa_dbus_send_proplist_variant_reply(conn, msg, s->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ dbus_uint32_t idx = 0;
+ dbus_uint32_t sample_format = 0;
+ dbus_uint32_t sample_rate = 0;
+ dbus_uint32_t channels[PA_CHANNELS_MAX];
+ dbus_uint32_t default_volume[PA_CHANNELS_MAX];
+ dbus_uint64_t duration = 0;
+ dbus_uint32_t bytes = 0;
+ unsigned i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ idx = s->sample->index;
+ if (s->sample->memchunk.memblock) {
+ sample_format = s->sample->sample_spec.format;
+ sample_rate = s->sample->sample_spec.rate;
+ for (i = 0; i < s->sample->channel_map.channels; ++i)
+ channels[i] = s->sample->channel_map.map[i];
+ duration = pa_bytes_to_usec(s->sample->memchunk.length, &s->sample->sample_spec);
+ bytes = s->sample->memchunk.length;
+ }
+ if (s->sample->volume_is_set) {
+ for (i = 0; i < s->sample->volume.channels; ++i)
+ default_volume[i] = s->sample->volume.values[i];
+ }
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &s->sample->name);
+
+ if (s->sample->memchunk.memblock) {
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &sample_rate);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, s->sample->channel_map.channels);
+ }
+
+ if (s->sample->volume_is_set)
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_VOLUME].property_name, DBUS_TYPE_UINT32, default_volume, s->sample->volume.channels);
+
+ if (s->sample->memchunk.memblock) {
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DURATION].property_name, DBUS_TYPE_UINT64, &duration);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BYTES].property_name, DBUS_TYPE_UINT32, &bytes);
+ }
+
+ pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, s->proplist);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+ dbus_message_unref(reply);
+}
+
+static void handle_play(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+ DBusMessageIter msg_iter;
+ dbus_uint32_t volume = 0;
+ pa_proplist *property_list = NULL;
+ pa_sink *sink = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+ dbus_message_iter_get_basic(&msg_iter, &volume);
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter)))
+ return;
+
+ if (volume > PA_VOLUME_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume.");
+ goto finish;
+ }
+
+ if (!(sink = pa_namereg_get_default_sink(s->sample->core))) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+ "Can't play sample %s, because there are no sinks available.", s->sample->name);
+ goto finish;
+ }
+
+ if (pa_scache_play_item(s->sample->core, s->sample->name, sink, volume, property_list, NULL) < 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Playing sample %s failed.", s->sample->name);
+ goto finish;
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+finish:
+ if (property_list)
+ pa_proplist_free(property_list);
+}
+
+static void handle_play_to_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+ DBusMessageIter msg_iter;
+ const char *sink_path = NULL;
+ dbus_uint32_t volume = 0;
+ pa_proplist *property_list = NULL;
+ pa_sink *sink = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+ dbus_message_iter_get_basic(&msg_iter, &sink_path);
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ dbus_message_iter_get_basic(&msg_iter, &volume);
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter)))
+ return;
+
+ if (!(sink = pa_dbusiface_core_get_sink(s->core, sink_path))) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", sink_path);
+ goto finish;
+ }
+
+ if (volume > PA_VOLUME_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume.");
+ goto finish;
+ }
+
+ if (pa_scache_play_item(s->sample->core, s->sample->name, sink, volume, property_list, NULL) < 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Playing sample %s failed.", s->sample->name);
+ goto finish;
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+finish:
+ if (property_list)
+ pa_proplist_free(property_list);
+}
+
+static void handle_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (pa_scache_remove_item(s->sample->core, s->sample->name) < 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Removing sample %s failed.", s->sample->name);
+ return;
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ pa_dbusiface_sample *s = userdata;
+ DBusMessage *signal = NULL;
+
+ pa_assert(c);
+ pa_assert(s);
+
+ /* We can't use idx != s->sample->index, because the s->sample pointer may
+ * be stale at this point. */
+ if (pa_idxset_get_by_index(c->scache, idx) != s->sample)
+ return;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+ return;
+
+ if (!pa_proplist_equal(s->proplist, s->sample->proplist)) {
+ DBusMessageIter msg_iter;
+
+ pa_proplist_update(s->proplist, PA_UPDATE_SET, s->sample->proplist);
+
+ pa_assert_se(signal = dbus_message_new_signal(s->path,
+ PA_DBUSIFACE_SAMPLE_INTERFACE,
+ signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+ dbus_message_iter_init_append(signal, &msg_iter);
+ pa_dbus_append_proplist(&msg_iter, s->proplist);
+
+ pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+}
+
+pa_dbusiface_sample *pa_dbusiface_sample_new(pa_dbusiface_core *core, pa_scache_entry *sample) {
+ pa_dbusiface_sample *s = NULL;
+
+ pa_assert(core);
+ pa_assert(sample);
+
+ s = pa_xnew0(pa_dbusiface_sample, 1);
+ s->core = core;
+ s->sample = sample;
+ s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, sample->index);
+ s->proplist = pa_proplist_copy(sample->proplist);
+ s->dbus_protocol = pa_dbus_protocol_get(sample->core);
+ s->subscription = pa_subscription_new(sample->core, PA_SUBSCRIPTION_MASK_SAMPLE_CACHE, subscription_cb, s);
+
+ pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &sample_interface_info, s) >= 0);
+
+ return s;
+}
+
+void pa_dbusiface_sample_free(pa_dbusiface_sample *s) {
+ pa_assert(s);
+
+ pa_assert_se(pa_dbus_protocol_remove_interface(s->dbus_protocol, s->path, sample_interface_info.name) >= 0);
+
+ pa_proplist_free(s->proplist);
+ pa_dbus_protocol_unref(s->dbus_protocol);
+ pa_subscription_free(s->subscription);
+
+ pa_xfree(s->path);
+ pa_xfree(s);
+}
+
+const char *pa_dbusiface_sample_get_path(pa_dbusiface_sample *s) {
+ pa_assert(s);
+
+ return s->path;
+}
diff --git a/src/modules/dbus/iface-sample.h b/src/modules/dbus/iface-sample.h
new file mode 100644
index 00000000..f1947ce8
--- /dev/null
+++ b/src/modules/dbus/iface-sample.h
@@ -0,0 +1,45 @@
+#ifndef foodbusifacesamplehfoo
+#define foodbusifacesamplehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ 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.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Sample.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Sample interface
+ * documentation.
+ */
+
+#include <pulsecore/core-scache.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_SAMPLE_INTERFACE PA_DBUS_CORE_INTERFACE ".Sample"
+
+typedef struct pa_dbusiface_sample pa_dbusiface_sample;
+
+pa_dbusiface_sample *pa_dbusiface_sample_new(pa_dbusiface_core *core, pa_scache_entry *sample);
+void pa_dbusiface_sample_free(pa_dbusiface_sample *c);
+
+const char *pa_dbusiface_sample_get_path(pa_dbusiface_sample *c);
+
+#endif
diff --git a/src/modules/dbus/iface-stream.c b/src/modules/dbus/iface-stream.c
new file mode 100644
index 00000000..04a45e6c
--- /dev/null
+++ b/src/modules/dbus/iface-stream.c
@@ -0,0 +1,894 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+ Copyright 2009 Vincent Filali-Ansary <filali.v@azurdigitalnetworks.net>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-stream.h"
+
+#define PLAYBACK_OBJECT_NAME "playback_stream"
+#define RECORD_OBJECT_NAME "record_stream"
+
+enum stream_type {
+ STREAM_TYPE_PLAYBACK,
+ STREAM_TYPE_RECORD
+};
+
+struct pa_dbusiface_stream {
+ pa_dbusiface_core *core;
+
+ union {
+ pa_sink_input *sink_input;
+ pa_source_output *source_output;
+ };
+ enum stream_type type;
+ char *path;
+ union {
+ pa_sink *sink;
+ pa_source *source;
+ };
+ uint32_t sample_rate;
+ pa_cvolume volume;
+ dbus_bool_t mute;
+ pa_proplist *proplist;
+
+ pa_dbus_protocol *dbus_protocol;
+ pa_subscription *subscription;
+ pa_hook_slot *send_event_slot;
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_client(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_buffer_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_device_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_resample_method(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_move(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum property_handler_index {
+ PROPERTY_HANDLER_INDEX,
+ PROPERTY_HANDLER_DRIVER,
+ PROPERTY_HANDLER_OWNER_MODULE,
+ PROPERTY_HANDLER_CLIENT,
+ PROPERTY_HANDLER_DEVICE,
+ PROPERTY_HANDLER_SAMPLE_FORMAT,
+ PROPERTY_HANDLER_SAMPLE_RATE,
+ PROPERTY_HANDLER_CHANNELS,
+ PROPERTY_HANDLER_VOLUME,
+ PROPERTY_HANDLER_MUTE,
+ PROPERTY_HANDLER_BUFFER_LATENCY,
+ PROPERTY_HANDLER_DEVICE_LATENCY,
+ PROPERTY_HANDLER_RESAMPLE_METHOD,
+ PROPERTY_HANDLER_PROPERTY_LIST,
+ PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+ [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL },
+ [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL },
+ [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL },
+ [PROPERTY_HANDLER_CLIENT] = { .property_name = "Client", .type = "o", .get_cb = handle_get_client, .set_cb = NULL },
+ [PROPERTY_HANDLER_DEVICE] = { .property_name = "Device", .type = "o", .get_cb = handle_get_device, .set_cb = NULL },
+ [PROPERTY_HANDLER_SAMPLE_FORMAT] = { .property_name = "SampleFormat", .type = "u", .get_cb = handle_get_sample_format, .set_cb = NULL },
+ [PROPERTY_HANDLER_SAMPLE_RATE] = { .property_name = "SampleRate", .type = "u", .get_cb = handle_get_sample_rate, .set_cb = NULL },
+ [PROPERTY_HANDLER_CHANNELS] = { .property_name = "Channels", .type = "au", .get_cb = handle_get_channels, .set_cb = NULL },
+ [PROPERTY_HANDLER_VOLUME] = { .property_name = "Volume", .type = "au", .get_cb = handle_get_volume, .set_cb = handle_set_volume },
+ [PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_get_mute, .set_cb = handle_set_mute },
+ [PROPERTY_HANDLER_BUFFER_LATENCY] = { .property_name = "BufferLatency", .type = "t", .get_cb = handle_get_buffer_latency, .set_cb = NULL },
+ [PROPERTY_HANDLER_DEVICE_LATENCY] = { .property_name = "DeviceLatency", .type = "t", .get_cb = handle_get_device_latency, .set_cb = NULL },
+ [PROPERTY_HANDLER_RESAMPLE_METHOD] = { .property_name = "ResampleMethod", .type = "s", .get_cb = handle_get_resample_method, .set_cb = NULL },
+ [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL }
+};
+
+enum method_handler_index {
+ METHOD_HANDLER_MOVE,
+ METHOD_HANDLER_KILL,
+ METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info move_args[] = { { "device", "o", "in" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+ [METHOD_HANDLER_MOVE] = {
+ .method_name = "Move",
+ .arguments = move_args,
+ .n_arguments = sizeof(move_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_move },
+ [METHOD_HANDLER_KILL] = {
+ .method_name = "Kill",
+ .arguments = NULL,
+ .n_arguments = 0,
+ .receive_cb = handle_kill }
+};
+
+enum signal_index {
+ SIGNAL_DEVICE_UPDATED,
+ SIGNAL_SAMPLE_RATE_UPDATED,
+ SIGNAL_VOLUME_UPDATED,
+ SIGNAL_MUTE_UPDATED,
+ SIGNAL_PROPERTY_LIST_UPDATED,
+ SIGNAL_STREAM_EVENT,
+ SIGNAL_MAX
+};
+
+static pa_dbus_arg_info device_updated_args[] = { { "device", "o", NULL } };
+static pa_dbus_arg_info sample_rate_updated_args[] = { { "sample_rate", "u", NULL } };
+static pa_dbus_arg_info volume_updated_args[] = { { "volume", "au", NULL } };
+static pa_dbus_arg_info mute_updated_args[] = { { "muted", "b", NULL } };
+static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
+static pa_dbus_arg_info stream_event_args[] = { { "name", "s", NULL }, { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+ [SIGNAL_DEVICE_UPDATED] = { .name = "DeviceUpdated", .arguments = device_updated_args, .n_arguments = 1 },
+ [SIGNAL_SAMPLE_RATE_UPDATED] = { .name = "SampleRateUpdated", .arguments = sample_rate_updated_args, .n_arguments = 1 },
+ [SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = volume_updated_args, .n_arguments = 1 },
+ [SIGNAL_MUTE_UPDATED] = { .name = "MuteUpdated", .arguments = mute_updated_args, .n_arguments = 1 },
+ [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 },
+ [SIGNAL_STREAM_EVENT] = { .name = "StreamEvent", .arguments = stream_event_args, .n_arguments = sizeof(stream_event_args) / sizeof(pa_dbus_arg_info) }
+};
+
+static pa_dbus_interface_info stream_interface_info = {
+ .name = PA_DBUSIFACE_STREAM_INTERFACE,
+ .method_handlers = method_handlers,
+ .n_method_handlers = METHOD_HANDLER_MAX,
+ .property_handlers = property_handlers,
+ .n_property_handlers = PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_get_all,
+ .signals = signals,
+ .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ dbus_uint32_t idx;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ idx = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->index : s->source_output->index;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ const char *driver = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ driver = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->driver : s->source_output->driver;
+
+ if (!driver) {
+ if (s->type == STREAM_TYPE_PLAYBACK)
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Playback stream %u doesn't have a driver.", s->sink_input->index);
+ else
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Record stream %u doesn't have a driver.", s->source_output->index);
+ return;
+ }
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &driver);
+}
+
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ pa_module *owner_module = NULL;
+ const char *object_path = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ owner_module = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->module : s->source_output->module;
+
+ if (!owner_module) {
+ if (s->type == STREAM_TYPE_PLAYBACK)
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Playback stream %u doesn't have an owner module.", s->sink_input->index);
+ else
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Record stream %u doesn't have an owner module.", s->source_output->index);
+ return;
+ }
+
+ object_path = pa_dbusiface_core_get_module_path(s->core, owner_module);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_client(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ pa_client *client = NULL;
+ const char *object_path = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ client = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->client : s->source_output->client;
+
+ if (!client) {
+ if (s->type == STREAM_TYPE_PLAYBACK)
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Playback stream %u isn't associated to any client.", s->sink_input->index);
+ else
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+ "Record stream %u isn't associated to any client.", s->source_output->index);
+ return;
+ }
+
+ object_path = pa_dbusiface_core_get_client_path(s->core, client);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ const char *device = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (s->type == STREAM_TYPE_PLAYBACK)
+ device = pa_dbusiface_core_get_sink_path(s->core, s->sink);
+ else
+ device = pa_dbusiface_core_get_source_path(s->core, s->source);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &device);
+}
+
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ dbus_uint32_t sample_format = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ sample_format = (s->type == STREAM_TYPE_PLAYBACK)
+ ? s->sink_input->sample_spec.format
+ : s->source_output->sample_spec.format;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format);
+}
+
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &s->sample_rate);
+}
+
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ pa_channel_map *channel_map = NULL;
+ dbus_uint32_t channels[PA_CHANNELS_MAX];
+ unsigned i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ channel_map = (s->type == STREAM_TYPE_PLAYBACK) ? &s->sink_input->channel_map : &s->source_output->channel_map;
+
+ for (i = 0; i < channel_map->channels; ++i)
+ channels[i] = channel_map->map[i];
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, channel_map->channels);
+}
+
+static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ dbus_uint32_t volume[PA_CHANNELS_MAX];
+ unsigned i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (s->type == STREAM_TYPE_RECORD) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have volume.");
+ return;
+ }
+
+ for (i = 0; i < s->volume.channels; ++i)
+ volume[i] = s->volume.values[i];
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, volume, s->volume.channels);
+}
+
+static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ DBusMessageIter array_iter;
+ int stream_channels = 0;
+ dbus_uint32_t *volume = NULL;
+ int n_volume_entries = 0;
+ pa_cvolume new_vol;
+ int i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(s);
+
+ if (s->type == STREAM_TYPE_RECORD) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have volume.");
+ return;
+ }
+
+ pa_cvolume_init(&new_vol);
+
+ stream_channels = s->sink_input->channel_map.channels;
+
+ new_vol.channels = stream_channels;
+
+ dbus_message_iter_recurse(iter, &array_iter);
+ dbus_message_iter_get_fixed_array(&array_iter, &volume, &n_volume_entries);
+
+ if (n_volume_entries != stream_channels) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+ "Expected %u volume entries, got %u.", stream_channels, n_volume_entries);
+ return;
+ }
+
+ for (i = 0; i < n_volume_entries; ++i) {
+ if (volume[i] > PA_VOLUME_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too large volume value: %u", volume[i]);
+ return;
+ }
+ new_vol.values[i] = volume[i];
+ }
+
+ pa_sink_input_set_volume(s->sink_input, &new_vol, TRUE, TRUE);
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (s->type == STREAM_TYPE_RECORD) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have mute.");
+ return;
+ }
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &s->mute);
+}
+
+static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ dbus_bool_t mute = FALSE;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(s);
+
+ dbus_message_iter_get_basic(iter, &mute);
+
+ if (s->type == STREAM_TYPE_RECORD) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have mute.");
+ return;
+ }
+
+ pa_sink_input_set_mute(s->sink_input, mute, TRUE);
+
+ pa_dbus_send_empty_reply(conn, msg);
+};
+
+static void handle_get_buffer_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ dbus_uint64_t buffer_latency = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (s->type == STREAM_TYPE_PLAYBACK)
+ buffer_latency = pa_sink_input_get_latency(s->sink_input, NULL);
+ else
+ buffer_latency = pa_source_output_get_latency(s->source_output, NULL);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &buffer_latency);
+}
+
+static void handle_get_device_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ dbus_uint64_t device_latency = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (s->type == STREAM_TYPE_PLAYBACK)
+ pa_sink_input_get_latency(s->sink_input, &device_latency);
+ else
+ pa_source_output_get_latency(s->source_output, &device_latency);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &device_latency);
+}
+
+static void handle_get_resample_method(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ const char *resample_method = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (s->type == STREAM_TYPE_PLAYBACK)
+ resample_method = pa_resample_method_to_string(s->sink_input->actual_resample_method);
+ else
+ resample_method = pa_resample_method_to_string(s->source_output->actual_resample_method);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &resample_method);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ pa_dbus_send_proplist_variant_reply(conn, msg, s->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ dbus_uint32_t idx = 0;
+ const char *driver = NULL;
+ pa_module *owner_module = NULL;
+ const char *owner_module_path = NULL;
+ pa_client *client = NULL;
+ const char *client_path = NULL;
+ const char *device = NULL;
+ dbus_uint32_t sample_format = 0;
+ pa_channel_map *channel_map = NULL;
+ dbus_uint32_t channels[PA_CHANNELS_MAX];
+ dbus_uint32_t volume[PA_CHANNELS_MAX];
+ dbus_uint64_t buffer_latency = 0;
+ dbus_uint64_t device_latency = 0;
+ const char *resample_method = NULL;
+ unsigned i = 0;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (s->type == STREAM_TYPE_PLAYBACK) {
+ idx = s->sink_input->index;
+ driver = s->sink_input->driver;
+ owner_module = s->sink_input->module;
+ client = s->sink_input->client;
+ device = pa_dbusiface_core_get_sink_path(s->core, s->sink);
+ sample_format = s->sink_input->sample_spec.format;
+ channel_map = &s->sink_input->channel_map;
+ for (i = 0; i < s->volume.channels; ++i)
+ volume[i] = s->volume.values[i];
+ buffer_latency = pa_sink_input_get_latency(s->sink_input, &device_latency);
+ resample_method = pa_resample_method_to_string(s->sink_input->actual_resample_method);
+ } else {
+ idx = s->source_output->index;
+ driver = s->source_output->driver;
+ owner_module = s->source_output->module;
+ client = s->source_output->client;
+ device = pa_dbusiface_core_get_source_path(s->core, s->source);
+ sample_format = s->source_output->sample_spec.format;
+ channel_map = &s->source_output->channel_map;
+ buffer_latency = pa_source_output_get_latency(s->source_output, &device_latency);
+ resample_method = pa_resample_method_to_string(s->source_output->actual_resample_method);
+ }
+ if (owner_module)
+ owner_module_path = pa_dbusiface_core_get_module_path(s->core, owner_module);
+ if (client)
+ client_path = pa_dbusiface_core_get_client_path(s->core, client);
+ for (i = 0; i < channel_map->channels; ++i)
+ channels[i] = channel_map->map[i];
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+
+ if (driver)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &driver);
+
+ if (owner_module)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module_path);
+
+ if (client)
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CLIENT].property_name, DBUS_TYPE_OBJECT_PATH, &client_path);
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &s->sample_rate);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, channel_map->channels);
+
+ if (s->type == STREAM_TYPE_PLAYBACK) {
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME].property_name, DBUS_TYPE_UINT32, volume, s->volume.channels);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &s->mute);
+ }
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BUFFER_LATENCY].property_name, DBUS_TYPE_UINT64, &buffer_latency);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEVICE_LATENCY].property_name, DBUS_TYPE_UINT64, &device_latency);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RESAMPLE_METHOD].property_name, DBUS_TYPE_STRING, &resample_method);
+ pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, s->proplist);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+ dbus_message_unref(reply);
+}
+
+static void handle_move(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ const char *device = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &device, DBUS_TYPE_INVALID));
+
+ if (s->type == STREAM_TYPE_PLAYBACK) {
+ pa_sink *sink = pa_dbusiface_core_get_sink(s->core, device);
+
+ if (!sink) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", device);
+ return;
+ }
+
+ if (pa_sink_input_move_to(s->sink_input, sink, TRUE) < 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+ "Moving playback stream %u to sink %s failed.", s->sink_input->index, sink->name);
+ return;
+ }
+ } else {
+ pa_source *source = pa_dbusiface_core_get_source(s->core, device);
+
+ if (!source) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", device);
+ return;
+ }
+
+ if (pa_source_output_move_to(s->source_output, source, TRUE) < 0) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+ "Moving record stream %u to source %s failed.", s->source_output->index, source->name);
+ return;
+ }
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(s);
+
+ if (s->type == STREAM_TYPE_PLAYBACK)
+ pa_sink_input_kill(s->sink_input);
+ else
+ pa_source_output_kill(s->source_output);
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ pa_dbusiface_stream *s = userdata;
+ DBusMessage *signal = NULL;
+ const char *new_device_path = NULL;
+ uint32_t new_sample_rate = 0;
+ pa_proplist *new_proplist = NULL;
+ unsigned i = 0;
+
+ pa_assert(c);
+ pa_assert(s);
+
+ if ((s->type == STREAM_TYPE_PLAYBACK && idx != s->sink_input->index)
+ || (s->type == STREAM_TYPE_RECORD && idx != s->source_output->index))
+ return;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+ return;
+
+ pa_assert(((s->type == STREAM_TYPE_PLAYBACK)
+ && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT))
+ || ((s->type == STREAM_TYPE_RECORD)
+ && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT)));
+
+ if (s->type == STREAM_TYPE_PLAYBACK) {
+ pa_sink *new_sink = s->sink_input->sink;
+
+ if (s->sink != new_sink) {
+ pa_sink_unref(s->sink);
+ s->sink = pa_sink_ref(new_sink);
+
+ new_device_path = pa_dbusiface_core_get_sink_path(s->core, new_sink);
+
+ pa_assert_se(signal = dbus_message_new_signal(s->path,
+ PA_DBUSIFACE_STREAM_INTERFACE,
+ signals[SIGNAL_DEVICE_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+ } else {
+ pa_source *new_source = s->source_output->source;
+
+ if (s->source != new_source) {
+ pa_source_unref(s->source);
+ s->source = pa_source_ref(new_source);
+
+ new_device_path = pa_dbusiface_core_get_source_path(s->core, new_source);
+
+ pa_assert_se(signal = dbus_message_new_signal(s->path,
+ PA_DBUSIFACE_STREAM_INTERFACE,
+ signals[SIGNAL_DEVICE_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+ }
+
+ new_sample_rate = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->sample_spec.rate : s->source_output->sample_spec.rate;
+
+ if (s->sample_rate != new_sample_rate) {
+ s->sample_rate = new_sample_rate;
+
+ pa_assert_se(signal = dbus_message_new_signal(s->path,
+ PA_DBUSIFACE_STREAM_INTERFACE,
+ signals[SIGNAL_SAMPLE_RATE_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_UINT32, &s->sample_rate, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+
+ if (s->type == STREAM_TYPE_PLAYBACK) {
+ pa_cvolume new_volume;
+ pa_bool_t new_mute = FALSE;
+
+ pa_sink_input_get_volume(s->sink_input, &new_volume, TRUE);
+
+ if (!pa_cvolume_equal(&s->volume, &new_volume)) {
+ dbus_uint32_t volume[PA_CHANNELS_MAX];
+ dbus_uint32_t *volume_ptr = volume;
+
+ s->volume = new_volume;
+
+ for (i = 0; i < s->volume.channels; ++i)
+ volume[i] = s->volume.values[i];
+
+ pa_assert_se(signal = dbus_message_new_signal(s->path,
+ PA_DBUSIFACE_STREAM_INTERFACE,
+ signals[SIGNAL_VOLUME_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &volume_ptr, s->volume.channels,
+ DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+
+ new_mute = pa_sink_input_get_mute(s->sink_input);
+
+ if (s->mute != new_mute) {
+ s->mute = new_mute;
+
+ pa_assert_se(signal = dbus_message_new_signal(s->path,
+ PA_DBUSIFACE_STREAM_INTERFACE,
+ signals[SIGNAL_MUTE_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_BOOLEAN, &s->mute, DBUS_TYPE_INVALID));
+
+ pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+ }
+
+ new_proplist = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->proplist : s->source_output->proplist;
+
+ if (!pa_proplist_equal(s->proplist, new_proplist)) {
+ DBusMessageIter msg_iter;
+
+ pa_proplist_update(s->proplist, PA_UPDATE_SET, new_proplist);
+
+ pa_assert_se(signal = dbus_message_new_signal(s->path,
+ PA_DBUSIFACE_STREAM_INTERFACE,
+ signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+ dbus_message_iter_init_append(signal, &msg_iter);
+ pa_dbus_append_proplist(&msg_iter, s->proplist);
+
+ pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+ dbus_message_unref(signal);
+ signal = NULL;
+ }
+}
+
+static pa_hook_result_t send_event_cb(void *hook_data, void *call_data, void *slot_data) {
+ pa_dbusiface_stream *s = slot_data;
+ DBusMessage *signal = NULL;
+ DBusMessageIter msg_iter;
+ const char *name = NULL;
+ pa_proplist *property_list = NULL;
+
+ pa_assert(call_data);
+ pa_assert(s);
+
+ if (s->type == STREAM_TYPE_PLAYBACK) {
+ pa_sink_input_send_event_hook_data *data = call_data;
+
+ if (data->sink_input != s->sink_input)
+ return PA_HOOK_OK;
+
+ name = data->event;
+ property_list = data->data;
+ } else {
+ pa_source_output_send_event_hook_data *data = call_data;
+
+ if (data->source_output != s->source_output)
+ return PA_HOOK_OK;
+
+ name = data->event;
+ property_list = data->data;
+ }
+
+ pa_assert_se(signal = dbus_message_new_signal(s->path,
+ PA_DBUSIFACE_STREAM_INTERFACE,
+ signals[SIGNAL_STREAM_EVENT].name));
+ dbus_message_iter_init_append(signal, &msg_iter);
+ pa_assert_se(dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &name));
+ pa_dbus_append_proplist(&msg_iter, property_list);
+
+ pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+ dbus_message_unref(signal);
+
+ return PA_HOOK_OK;
+}
+
+pa_dbusiface_stream *pa_dbusiface_stream_new_playback(pa_dbusiface_core *core, pa_sink_input *sink_input) {
+ pa_dbusiface_stream *s;
+
+ pa_assert(core);
+ pa_assert(sink_input);
+
+ s = pa_xnew(pa_dbusiface_stream, 1);
+ s->core = core;
+ s->sink_input = pa_sink_input_ref(sink_input);
+ s->type = STREAM_TYPE_PLAYBACK;
+ s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, PLAYBACK_OBJECT_NAME, sink_input->index);
+ s->sink = pa_sink_ref(sink_input->sink);
+ s->sample_rate = sink_input->sample_spec.rate;
+ pa_sink_input_get_volume(sink_input, &s->volume, TRUE);
+ s->mute = pa_sink_input_get_mute(sink_input);
+ s->proplist = pa_proplist_copy(sink_input->proplist);
+ s->dbus_protocol = pa_dbus_protocol_get(sink_input->core);
+ s->subscription = pa_subscription_new(sink_input->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, subscription_cb, s);
+ s->send_event_slot = pa_hook_connect(&sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_SEND_EVENT],
+ PA_HOOK_NORMAL,
+ send_event_cb,
+ s);
+
+ pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &stream_interface_info, s) >= 0);
+
+ return s;
+}
+
+pa_dbusiface_stream *pa_dbusiface_stream_new_record(pa_dbusiface_core *core, pa_source_output *source_output) {
+ pa_dbusiface_stream *s;
+
+ pa_assert(core);
+ pa_assert(source_output);
+
+ s = pa_xnew(pa_dbusiface_stream, 1);
+ s->core = core;
+ s->source_output = pa_source_output_ref(source_output);
+ s->type = STREAM_TYPE_RECORD;
+ s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, RECORD_OBJECT_NAME, source_output->index);
+ s->source = pa_source_ref(source_output->source);
+ s->sample_rate = source_output->sample_spec.rate;
+ pa_cvolume_init(&s->volume);
+ s->mute = FALSE;
+ s->proplist = pa_proplist_copy(source_output->proplist);
+ s->dbus_protocol = pa_dbus_protocol_get(source_output->core);
+ s->subscription = pa_subscription_new(source_output->core, PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscription_cb, s);
+ s->send_event_slot = pa_hook_connect(&source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT],
+ PA_HOOK_NORMAL,
+ send_event_cb,
+ s);
+
+ pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &stream_interface_info, s) >= 0);
+
+ return s;
+}
+
+void pa_dbusiface_stream_free(pa_dbusiface_stream *s) {
+ pa_assert(s);
+
+ pa_assert_se(pa_dbus_protocol_remove_interface(s->dbus_protocol, s->path, stream_interface_info.name) >= 0);
+
+ if (s->type == STREAM_TYPE_PLAYBACK) {
+ pa_sink_input_unref(s->sink_input);
+ pa_sink_unref(s->sink);
+ } else {
+ pa_source_output_unref(s->source_output);
+ pa_source_unref(s->source);
+ }
+
+ pa_proplist_free(s->proplist);
+ pa_dbus_protocol_unref(s->dbus_protocol);
+ pa_subscription_free(s->subscription);
+ pa_hook_slot_free(s->send_event_slot);
+
+ pa_xfree(s->path);
+ pa_xfree(s);
+}
+
+const char *pa_dbusiface_stream_get_path(pa_dbusiface_stream *s) {
+ pa_assert(s);
+
+ return s->path;
+}
diff --git a/src/modules/dbus/iface-stream.h b/src/modules/dbus/iface-stream.h
new file mode 100644
index 00000000..036b4e7e
--- /dev/null
+++ b/src/modules/dbus/iface-stream.h
@@ -0,0 +1,47 @@
+#ifndef foodbusifacestreamhfoo
+#define foodbusifacestreamhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ 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.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Stream.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Stream interface
+ * documentation.
+ */
+
+#include <pulsecore/protocol-dbus.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/source-output.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_STREAM_INTERFACE PA_DBUS_CORE_INTERFACE ".Stream"
+
+typedef struct pa_dbusiface_stream pa_dbusiface_stream;
+
+pa_dbusiface_stream *pa_dbusiface_stream_new_playback(pa_dbusiface_core *core, pa_sink_input *sink_input);
+pa_dbusiface_stream *pa_dbusiface_stream_new_record(pa_dbusiface_core *core, pa_source_output *source_output);
+void pa_dbusiface_stream_free(pa_dbusiface_stream *s);
+
+const char *pa_dbusiface_stream_get_path(pa_dbusiface_stream *s);
+
+#endif
diff --git a/src/modules/dbus/module-dbus-protocol.c b/src/modules/dbus/module-dbus-protocol.c
new file mode 100644
index 00000000..11064c33
--- /dev/null
+++ b/src/modules/dbus/module-dbus-protocol.c
@@ -0,0 +1,607 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+ Copyright 2006 Lennart Poettering
+ Copyright 2006 Shams E. King
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulse/mainloop-api.h>
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/client.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/module.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-client.h"
+#include "iface-core.h"
+
+#include "module-dbus-protocol-symdef.h"
+
+PA_MODULE_DESCRIPTION("D-Bus interface");
+PA_MODULE_USAGE(
+ "access=local|remote|local,remote "
+ "tcp_port=<port number>");
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_AUTHOR("Tanu Kaskinen");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+
+#define CLEANUP_INTERVAL 10 /* seconds */
+
+enum server_type {
+ SERVER_TYPE_LOCAL,
+ SERVER_TYPE_TCP
+};
+
+struct server;
+struct connection;
+
+struct userdata {
+ pa_module *module;
+ pa_bool_t local_access;
+ pa_bool_t remote_access;
+ uint32_t tcp_port;
+
+ struct server *local_server;
+ struct server *tcp_server;
+
+ pa_idxset *connections;
+
+ pa_time_event *cleanup_event;
+
+ pa_dbus_protocol *dbus_protocol;
+ pa_dbusiface_core *core_iface;
+};
+
+struct server {
+ struct userdata *userdata;
+ enum server_type type;
+ DBusServer *dbus_server;
+};
+
+struct connection {
+ struct server *server;
+ pa_dbus_wrap_connection *wrap_conn;
+ pa_client *client;
+};
+
+static const char* const valid_modargs[] = {
+ "access",
+ "tcp_port",
+ NULL
+};
+
+static void connection_free(struct connection *c) {
+ pa_assert(c);
+
+ pa_assert_se(pa_dbus_protocol_unregister_connection(c->server->userdata->dbus_protocol, pa_dbus_wrap_connection_get(c->wrap_conn)) >= 0);
+
+ pa_client_free(c->client);
+ pa_assert_se(pa_idxset_remove_by_data(c->server->userdata->connections, c, NULL));
+ pa_dbus_wrap_connection_free(c->wrap_conn);
+ pa_xfree(c);
+}
+
+/* Called from pa_client_kill(). */
+static void client_kill_cb(pa_client *c) {
+ struct connection *conn;
+
+ pa_assert(c);
+ pa_assert(c->userdata);
+
+ conn = c->userdata;
+ connection_free(conn);
+ c->userdata = NULL;
+
+ pa_log_info("Connection killed.");
+}
+
+/* Called from pa_client_send_event(). */
+static void client_send_event_cb(pa_client *c, const char *name, pa_proplist *data) {
+ struct connection *conn = NULL;
+ DBusMessage *signal = NULL;
+ DBusMessageIter msg_iter;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(data);
+ pa_assert(c->userdata);
+
+ conn = c->userdata;
+
+ pa_assert_se(signal = dbus_message_new_signal(pa_dbusiface_core_get_client_path(conn->server->userdata->core_iface, c),
+ PA_DBUSIFACE_CLIENT_INTERFACE,
+ "ClientEvent"));
+ dbus_message_iter_init_append(signal, &msg_iter);
+ pa_assert_se(dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &name));
+ pa_dbus_append_proplist(&msg_iter, data);
+
+ pa_assert_se(dbus_connection_send(pa_dbus_wrap_connection_get(conn->wrap_conn), signal, NULL));
+ dbus_message_unref(signal);
+}
+
+/* Called by D-Bus at the authentication phase. */
+static dbus_bool_t user_check_cb(DBusConnection *connection, unsigned long uid, void *data) {
+ pa_log_debug("Allowing connection by user %lu.", uid);
+
+ return TRUE;
+}
+
+/* Called by D-Bus when a new client connection is received. */
+static void connection_new_cb(DBusServer *dbus_server, DBusConnection *new_connection, void *data) {
+ struct server *s = data;
+ struct connection *c;
+ pa_client_new_data new_data;
+ pa_client *client;
+
+ pa_assert(new_connection);
+ pa_assert(s);
+
+ pa_client_new_data_init(&new_data);
+ new_data.module = s->userdata->module;
+ new_data.driver = __FILE__;
+ pa_proplist_sets(new_data.proplist, PA_PROP_APPLICATION_NAME, "D-Bus client");
+ client = pa_client_new(s->userdata->module->core, &new_data);
+ pa_client_new_data_done(&new_data);
+
+ if (!client) {
+ dbus_connection_close(new_connection);
+ return;
+ }
+
+ if (s->type == SERVER_TYPE_TCP || s->userdata->module->core->server_type == PA_SERVER_TYPE_SYSTEM) {
+ /* FIXME: Here we allow anyone from anywhere to access the server,
+ * anonymously. Access control should be configurable. */
+ dbus_connection_set_unix_user_function(new_connection, user_check_cb, NULL, NULL);
+ dbus_connection_set_allow_anonymous(new_connection, TRUE);
+ }
+
+ c = pa_xnew(struct connection, 1);
+ c->server = s;
+ c->wrap_conn = pa_dbus_wrap_connection_new_from_existing(s->userdata->module->core->mainloop, TRUE, new_connection);
+ c->client = client;
+
+ c->client->kill = client_kill_cb;
+ c->client->send_event = client_send_event_cb;
+ c->client->userdata = c;
+
+ pa_idxset_put(s->userdata->connections, c, NULL);
+
+ pa_assert_se(pa_dbus_protocol_register_connection(s->userdata->dbus_protocol, new_connection, c->client) >= 0);
+}
+
+/* Called by PA mainloop when a D-Bus fd watch event needs handling. */
+static void io_event_cb(pa_mainloop_api *mainloop, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
+ unsigned int flags = 0;
+ DBusWatch *watch = userdata;
+
+#if HAVE_DBUS_WATCH_GET_UNIX_FD
+ pa_assert(fd == dbus_watch_get_unix_fd(watch));
+#else
+ pa_assert(fd == dbus_watch_get_fd(watch));
+#endif
+
+ if (!dbus_watch_get_enabled(watch)) {
+ pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd);
+ return;
+ }
+
+ if (events & PA_IO_EVENT_INPUT)
+ flags |= DBUS_WATCH_READABLE;
+ if (events & PA_IO_EVENT_OUTPUT)
+ flags |= DBUS_WATCH_WRITABLE;
+ if (events & PA_IO_EVENT_HANGUP)
+ flags |= DBUS_WATCH_HANGUP;
+ if (events & PA_IO_EVENT_ERROR)
+ flags |= DBUS_WATCH_ERROR;
+
+ dbus_watch_handle(watch, flags);
+}
+
+/* Called by PA mainloop when a D-Bus timer event needs handling. */
+static void time_event_cb(pa_mainloop_api *mainloop, pa_time_event* e, const struct timeval *tv, void *userdata) {
+ DBusTimeout *timeout = userdata;
+
+ if (dbus_timeout_get_enabled(timeout)) {
+ struct timeval next = *tv;
+ dbus_timeout_handle(timeout);
+
+ /* restart it for the next scheduled time */
+ pa_timeval_add(&next, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000);
+ mainloop->time_restart(e, &next);
+ }
+}
+
+/* Translates D-Bus fd watch event flags to PA IO event flags. */
+static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) {
+ unsigned int flags;
+ pa_io_event_flags_t events = 0;
+
+ pa_assert(watch);
+
+ flags = dbus_watch_get_flags(watch);
+
+ /* no watch flags for disabled watches */
+ if (!dbus_watch_get_enabled(watch))
+ return PA_IO_EVENT_NULL;
+
+ if (flags & DBUS_WATCH_READABLE)
+ events |= PA_IO_EVENT_INPUT;
+ if (flags & DBUS_WATCH_WRITABLE)
+ events |= PA_IO_EVENT_OUTPUT;
+
+ return events | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR;
+}
+
+/* Called by D-Bus when a D-Bus fd watch event is added. */
+static dbus_bool_t watch_add_cb(DBusWatch *watch, void *data) {
+ struct server *s = data;
+ pa_mainloop_api *mainloop;
+ pa_io_event *ev;
+
+ pa_assert(watch);
+ pa_assert(s);
+
+ mainloop = s->userdata->module->core->mainloop;
+
+ ev = mainloop->io_new(
+ mainloop,
+#if HAVE_DBUS_WATCH_GET_UNIX_FD
+ dbus_watch_get_unix_fd(watch),
+#else
+ dbus_watch_get_fd(watch),
+#endif
+ get_watch_flags(watch), io_event_cb, watch);
+
+ dbus_watch_set_data(watch, ev, NULL);
+
+ return TRUE;
+}
+
+/* Called by D-Bus when a D-Bus fd watch event is removed. */
+static void watch_remove_cb(DBusWatch *watch, void *data) {
+ struct server *s = data;
+ pa_io_event *ev;
+
+ pa_assert(watch);
+ pa_assert(s);
+
+ if ((ev = dbus_watch_get_data(watch)))
+ s->userdata->module->core->mainloop->io_free(ev);
+}
+
+/* Called by D-Bus when a D-Bus fd watch event is toggled. */
+static void watch_toggled_cb(DBusWatch *watch, void *data) {
+ struct server *s = data;
+ pa_io_event *ev;
+
+ pa_assert(watch);
+ pa_assert(s);
+
+ pa_assert_se(ev = dbus_watch_get_data(watch));
+
+ /* get_watch_flags() checks if the watch is enabled */
+ s->userdata->module->core->mainloop->io_enable(ev, get_watch_flags(watch));
+}
+
+/* Called by D-Bus when a D-Bus timer event is added. */
+static dbus_bool_t timeout_add_cb(DBusTimeout *timeout, void *data) {
+ struct server *s = data;
+ pa_mainloop_api *mainloop;
+ pa_time_event *ev;
+ struct timeval tv;
+
+ pa_assert(timeout);
+ pa_assert(s);
+
+ if (!dbus_timeout_get_enabled(timeout))
+ return FALSE;
+
+ mainloop = s->userdata->module->core->mainloop;
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000);
+
+ ev = mainloop->time_new(mainloop, &tv, time_event_cb, timeout);
+
+ dbus_timeout_set_data(timeout, ev, NULL);
+
+ return TRUE;
+}
+
+/* Called by D-Bus when a D-Bus timer event is removed. */
+static void timeout_remove_cb(DBusTimeout *timeout, void *data) {
+ struct server *s = data;
+ pa_time_event *ev;
+
+ pa_assert(timeout);
+ pa_assert(s);
+
+ if ((ev = dbus_timeout_get_data(timeout)))
+ s->userdata->module->core->mainloop->time_free(ev);
+}
+
+/* Called by D-Bus when a D-Bus timer event is toggled. */
+static void timeout_toggled_cb(DBusTimeout *timeout, void *data) {
+ struct server *s = data;
+ pa_mainloop_api *mainloop;
+ pa_time_event *ev;
+
+ pa_assert(timeout);
+ pa_assert(s);
+
+ mainloop = s->userdata->module->core->mainloop;
+
+ pa_assert_se(ev = dbus_timeout_get_data(timeout));
+
+ if (dbus_timeout_get_enabled(timeout)) {
+ struct timeval tv;
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000);
+
+ mainloop->time_restart(ev, &tv);
+ } else
+ mainloop->time_restart(ev, NULL);
+}
+
+static void server_free(struct server *s) {
+ pa_assert(s);
+
+ if (s->dbus_server) {
+ dbus_server_disconnect(s->dbus_server);
+ dbus_server_unref(s->dbus_server);
+ }
+
+ pa_xfree(s);
+}
+
+static struct server *start_server(struct userdata *u, const char *address, enum server_type type) {
+ /* XXX: We assume that when we unref the DBusServer instance at module
+ * shutdown, nobody else holds any references to it. If we stop assuming
+ * that someday, dbus_server_set_new_connection_function,
+ * dbus_server_set_watch_functions and dbus_server_set_timeout_functions
+ * calls should probably register free callbacks, instead of providing NULL
+ * as they do now. */
+
+ struct server *s = NULL;
+ DBusError error;
+
+ pa_assert(u);
+ pa_assert(address);
+
+ dbus_error_init(&error);
+
+ s = pa_xnew0(struct server, 1);
+ s->userdata = u;
+ s->dbus_server = dbus_server_listen(address, &error);
+
+ if (dbus_error_is_set(&error)) {
+ pa_log("dbus_server_listen() failed: %s: %s", error.name, error.message);
+ goto fail;
+ }
+
+ dbus_server_set_new_connection_function(s->dbus_server, connection_new_cb, s, NULL);
+
+ if (!dbus_server_set_watch_functions(s->dbus_server, watch_add_cb, watch_remove_cb, watch_toggled_cb, s, NULL)) {
+ pa_log("dbus_server_set_watch_functions() ran out of memory.");
+ goto fail;
+ }
+
+ if (!dbus_server_set_timeout_functions(s->dbus_server, timeout_add_cb, timeout_remove_cb, timeout_toggled_cb, s, NULL)) {
+ pa_log("dbus_server_set_timeout_functions() ran out of memory.");
+ goto fail;
+ }
+
+ return s;
+
+fail:
+ if (s)
+ server_free(s);
+
+ dbus_error_free(&error);
+
+ return NULL;
+}
+
+static struct server *start_local_server(struct userdata *u) {
+ struct server *s = NULL;
+ char *address = NULL;
+
+ pa_assert(u);
+
+ address = pa_get_dbus_address_from_server_type(u->module->core->server_type);
+
+ s = start_server(u, address, SERVER_TYPE_LOCAL); /* May return NULL */
+
+ pa_xfree(address);
+
+ return s;
+}
+
+static struct server *start_tcp_server(struct userdata *u) {
+ struct server *s = NULL;
+ char *address = NULL;
+
+ pa_assert(u);
+
+ address = pa_sprintf_malloc("tcp:host=127.0.0.1,port=%u", u->tcp_port);
+
+ s = start_server(u, address, SERVER_TYPE_TCP); /* May return NULL */
+
+ pa_xfree(address);
+
+ return s;
+}
+
+static int get_access_arg(pa_modargs *ma, pa_bool_t *local_access, pa_bool_t *remote_access) {
+ const char *value = NULL;
+
+ pa_assert(ma);
+ pa_assert(local_access);
+ pa_assert(remote_access);
+
+ if (!(value = pa_modargs_get_value(ma, "access", NULL)))
+ return 0;
+
+ if (!strcmp(value, "local")) {
+ *local_access = TRUE;
+ *remote_access = FALSE;
+ } else if (!strcmp(value, "remote")) {
+ *local_access = FALSE;
+ *remote_access = TRUE;
+ } else if (!strcmp(value, "local,remote")) {
+ *local_access = TRUE;
+ *remote_access = TRUE;
+ } else
+ return -1;
+
+ return 0;
+}
+
+/* Frees dead client connections. Called every CLEANUP_INTERVAL seconds. */
+static void cleanup_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ struct connection *conn = NULL;
+ uint32_t idx;
+ struct timeval cleanup_timeval;
+ unsigned free_count = 0;
+
+ for (conn = pa_idxset_first(u->connections, &idx); conn; conn = pa_idxset_next(u->connections, &idx)) {
+ if (!dbus_connection_get_is_connected(pa_dbus_wrap_connection_get(conn->wrap_conn))) {
+ connection_free(conn);
+ ++free_count;
+ }
+ }
+
+ if (free_count > 0)
+ pa_log_debug("Freed %u dead D-Bus client connections.", free_count);
+
+ pa_gettimeofday(&cleanup_timeval);
+ cleanup_timeval.tv_sec += CLEANUP_INTERVAL;
+ u->module->core->mainloop->time_restart(e, &cleanup_timeval);
+}
+
+int pa__init(pa_module *m) {
+ struct userdata *u = NULL;
+ pa_modargs *ma = NULL;
+ struct timeval cleanup_timeval;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew0(struct userdata, 1);
+ u->module = m;
+ u->local_access = TRUE;
+ u->remote_access = FALSE;
+ u->tcp_port = PA_DBUS_DEFAULT_PORT;
+
+ if (get_access_arg(ma, &u->local_access, &u->remote_access) < 0) {
+ pa_log("Invalid access argument: '%s'", pa_modargs_get_value(ma, "access", NULL));
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_u32(ma, "tcp_port", &u->tcp_port) < 0 || u->tcp_port < 1 || u->tcp_port > 49150) {
+ pa_log("Invalid tcp_port argument: '%s'", pa_modargs_get_value(ma, "tcp_port", NULL));
+ goto fail;
+ }
+
+ if (u->local_access && !(u->local_server = start_local_server(u))) {
+ pa_log("Starting the local D-Bus server failed.");
+ goto fail;
+ }
+
+ if (u->remote_access && !(u->tcp_server = start_tcp_server(u))) {
+ pa_log("Starting the D-Bus server for remote connections failed.");
+ goto fail;
+ }
+
+ u->connections = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+ pa_gettimeofday(&cleanup_timeval);
+ cleanup_timeval.tv_sec += CLEANUP_INTERVAL;
+ u->cleanup_event = m->core->mainloop->time_new(m->core->mainloop, &cleanup_timeval, cleanup_cb, u);
+
+ u->dbus_protocol = pa_dbus_protocol_get(m->core);
+ u->core_iface = pa_dbusiface_core_new(m->core);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+/* Called by idxset when the connection set is freed. */
+static void connection_free_cb(void *p, void *userdata) {
+ struct connection *conn = p;
+
+ pa_assert(conn);
+
+ connection_free(conn);
+}
+
+void pa__done(pa_module *m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->core_iface)
+ pa_dbusiface_core_free(u->core_iface);
+
+ if (u->cleanup_event)
+ m->core->mainloop->time_free(u->cleanup_event);
+
+ if (u->connections)
+ pa_idxset_free(u->connections, connection_free_cb, NULL);
+
+ if (u->tcp_server)
+ server_free(u->tcp_server);
+
+ if (u->local_server)
+ server_free(u->local_server);
+
+ if (u->dbus_protocol)
+ pa_dbus_protocol_unref(u->dbus_protocol);
+
+ pa_xfree(u);
+ m->userdata = NULL;
+}
diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c
index c01ebbf6..5f31d688 100644
--- a/src/modules/gconf/module-gconf.c
+++ b/src/modules/gconf/module-gconf.c
@@ -52,9 +52,6 @@ PA_MODULE_LOAD_ONCE(TRUE);
#define MAX_MODULES 10
#define BUF_MAX 2048
-/* #undef PA_GCONF_HELPER */
-/* #define PA_GCONF_HELPER "/home/lennart/projects/pulseaudio/src/gconf-helper" */
-
struct module_item {
char *name;
char *args;
@@ -343,7 +340,11 @@ int pa__init(pa_module*m) {
u->io_event = NULL;
u->buf_fill = 0;
- if ((u->fd = pa_start_child_for_read(PA_GCONF_HELPER, NULL, &u->pid)) < 0)
+ if ((u->fd = pa_start_child_for_read(
+#if defined(__linux__) && !defined(__OPTIMIZE__)
+ pa_run_from_build_tree() ? PA_BUILDDIR "/gconf-helper" :
+#endif
+ PA_GCONF_HELPER, NULL, &u->pid)) < 0)
goto fail;
u->io_event = m->core->mainloop->io_new(
diff --git a/src/modules/hal-util.c b/src/modules/hal-util.c
index e2a2d8d7..2d59f51d 100644
--- a/src/modules/hal-util.c
+++ b/src/modules/hal-util.c
@@ -65,7 +65,7 @@ int pa_hal_get_info(pa_core *core, pa_proplist *p, int card) {
goto finish;
}
- if (!(udis = libhal_find_device_by_capability(hal, "sound", &n, &error)) < 0) {
+ if (!(udis = libhal_find_device_by_capability(hal, "sound", &n, &error))) {
pa_log_error("Couldn't find devices: %s: %s", error.name, error.message);
goto finish;
}
diff --git a/src/modules/jack/module-jack-sink.c b/src/modules/jack/module-jack-sink.c
index fc976fa7..9f3e071f 100644
--- a/src/modules/jack/module-jack-sink.c
+++ b/src/modules/jack/module-jack-sink.c
@@ -334,7 +334,7 @@ int pa__init(pa_module*m) {
goto fail;
}
- ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsInput);
+ ports = jack_get_ports(u->client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsInput);
channels = 0;
for (p = ports; *p; p++)
diff --git a/src/modules/jack/module-jack-source.c b/src/modules/jack/module-jack-source.c
index a898e0e5..6c68527b 100644
--- a/src/modules/jack/module-jack-source.c
+++ b/src/modules/jack/module-jack-source.c
@@ -286,7 +286,7 @@ int pa__init(pa_module*m) {
goto fail;
}
- ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput);
+ ports = jack_get_ports(u->client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsOutput);
channels = 0;
for (p = ports; *p; p++)
diff --git a/src/modules/module-always-sink.c b/src/modules/module-always-sink.c
index aee1c650..3d7de9c6 100644
--- a/src/modules/module-always-sink.c
+++ b/src/modules/module-always-sink.c
@@ -24,6 +24,7 @@
#endif
#include <pulse/xmalloc.h>
+#include <pulse/i18n.h>
#include <pulsecore/core.h>
#include <pulsecore/sink-input.h>
@@ -35,7 +36,7 @@
#include "module-always-sink-symdef.h"
PA_MODULE_AUTHOR("Colin Guthrie");
-PA_MODULE_DESCRIPTION("Always keeps at least one sink loaded even if it's a null one");
+PA_MODULE_DESCRIPTION(_("Always keeps at least one sink loaded even if it's a null one"));
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(TRUE);
PA_MODULE_USAGE(
@@ -78,7 +79,8 @@ static void load_null_sink_if_needed(pa_core *c, pa_sink *sink, struct userdata*
u->ignore = TRUE;
- t = pa_sprintf_malloc("sink_name=%s", u->sink_name);
+ t = pa_sprintf_malloc("sink_name=%s sink_properties='device.description=\"%s\"'", u->sink_name,
+ _("Dummy Output"));
m = pa_module_load(c, "module-null-sink", t);
u->null_module = m ? m->index : PA_INVALID_INDEX;
pa_xfree(t);
diff --git a/src/modules/module-cli.c b/src/modules/module-cli.c
index fd9452b4..c5ff4564 100644
--- a/src/modules/module-cli.c
+++ b/src/modules/module-cli.c
@@ -25,6 +25,8 @@
#include <stdio.h>
#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
#include <pulsecore/module.h>
#include <pulsecore/iochannel.h>
@@ -33,6 +35,8 @@
#include <pulsecore/log.h>
#include <pulsecore/modargs.h>
#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
#include "module-cli-symdef.h"
@@ -69,6 +73,7 @@ int pa__init(pa_module*m) {
pa_iochannel *io;
pa_modargs *ma;
pa_bool_t exit_on_eof = FALSE;
+ int fd;
pa_assert(m);
@@ -88,15 +93,28 @@ int pa__init(pa_module*m) {
}
if (pa_stdio_acquire() < 0) {
- pa_log("STDIN/STDUSE already in use.");
+ pa_log("STDIN/STDOUT already in use.");
goto fail;
}
- io = pa_iochannel_new(m->core->mainloop, STDIN_FILENO, STDOUT_FILENO);
- pa_iochannel_set_noclose(io, 1);
+ /* We try to open the controlling tty anew here. This has the
+ * benefit of giving us a new fd that doesn't share the O_NDELAY
+ * flag with fds 0, 1, or 2. Since pa_iochannel_xxx needs O_NDELAY
+ * on its fd using those fds directly could set O_NDELAY which
+ * fprintf() doesn't really like, resulting in truncated output
+ * of log messages, particularly because if stdout and stderr are
+ * dup'ed they share the same O_NDELAY, too. */
+
+ if ((fd = pa_open_cloexec("/dev/tty", O_RDWR|O_NONBLOCK, 0)) >= 0) {
+ io = pa_iochannel_new(m->core->mainloop, fd, fd);
+ pa_log_debug("Managed to open /dev/tty.");
+ } else {
+ io = pa_iochannel_new(m->core->mainloop, STDIN_FILENO, STDOUT_FILENO);
+ pa_iochannel_set_noclose(io, TRUE);
+ pa_log_debug("Failed to open /dev/tty, using stdin/stdout fds instead.");
+ }
m->userdata = pa_cli_new(m->core, io, m);
-
pa_cli_set_eof_callback(m->userdata, exit_on_eof ? eof_and_exit_cb : eof_and_unload_cb, m);
pa_modargs_free(ma);
@@ -114,7 +132,7 @@ fail:
void pa__done(pa_module*m) {
pa_assert(m);
- if (m->core->running_as_daemon == 0) {
+ if (m->userdata) {
pa_cli_free(m->userdata);
pa_stdio_release();
}
diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c
index 16de6890..a186c899 100644
--- a/src/modules/module-combine.c
+++ b/src/modules/module-combine.c
@@ -58,7 +58,7 @@ PA_MODULE_USAGE(
"sink_name=<name for the sink> "
"sink_properties=<properties for the sink> "
"slaves=<slave sinks> "
- "adjust_time=<seconds> "
+ "adjust_time=<how often to readjust rates in s> "
"resample_method=<method> "
"format=<sample format> "
"rate=<sample rate> "
@@ -69,7 +69,7 @@ PA_MODULE_USAGE(
#define MEMBLOCKQ_MAXLENGTH (1024*1024*16)
-#define DEFAULT_ADJUST_TIME 10
+#define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC)
#define BLOCK_USEC (PA_USEC_PER_MSEC * 200)
@@ -91,6 +91,7 @@ struct output {
pa_sink *sink;
pa_sink_input *sink_input;
+ pa_bool_t ignore_state_change;
pa_asyncmsgq *inq, /* Message queue from the sink thread to this sink input */
*outq; /* Message queue from this sink input to the sink thread */
@@ -99,9 +100,12 @@ struct output {
pa_memblockq *memblockq;
+ /* For communication of the stream latencies to the main thread */
pa_usec_t total_latency;
+ /* For coomunication of the stream parameters to the sink thread */
pa_atomic_t max_request;
+ pa_atomic_t requested_latency;
PA_LLIST_FIELDS(struct output);
};
@@ -116,7 +120,7 @@ struct userdata {
pa_rtpoll *rtpoll;
pa_time_event *time_event;
- uint32_t adjust_time;
+ pa_usec_t adjust_time;
pa_bool_t automatic;
pa_bool_t auto_desc;
@@ -125,8 +129,6 @@ struct userdata {
pa_resample_method_t resample_method;
- struct timeval adjust_timestamp;
-
pa_usec_t block_usec;
pa_idxset* outputs; /* managed in main context */
@@ -146,13 +148,16 @@ enum {
SINK_MESSAGE_REMOVE_OUTPUT,
SINK_MESSAGE_NEED,
SINK_MESSAGE_UPDATE_LATENCY,
- SINK_MESSAGE_UPDATE_MAX_REQUEST
+ SINK_MESSAGE_UPDATE_MAX_REQUEST,
+ SINK_MESSAGE_UPDATE_REQUESTED_LATENCY
};
enum {
SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX,
};
+static void output_disable(struct output *o);
+static void output_enable(struct output *o);
static void output_free(struct output *o);
static int output_create_sink_input(struct output *o);
@@ -172,7 +177,7 @@ static void adjust_rates(struct userdata *u) {
if (!PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)))
return;
- for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
+ PA_IDXSET_FOREACH(o, u->outputs, idx) {
pa_usec_t sink_latency;
if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))
@@ -189,6 +194,11 @@ static void adjust_rates(struct userdata *u) {
avg_total_latency += o->total_latency;
n++;
+
+ pa_log_debug("[%s] total=%0.2fms sink=%0.2fms ", o->sink->name, (double) o->total_latency / PA_USEC_PER_MSEC, (double) sink_latency / PA_USEC_PER_MSEC);
+
+ if (o->total_latency > 10*PA_USEC_PER_SEC)
+ pa_log_warn("[%s] Total latency of output is very high (%0.2fms), most likely the audio timing in one of your drivers is broken.", o->sink->name, (double) o->total_latency / PA_USEC_PER_MSEC);
}
if (min_total_latency == (pa_usec_t) -1)
@@ -203,22 +213,22 @@ static void adjust_rates(struct userdata *u) {
base_rate = u->sink->sample_spec.rate;
- for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
+ PA_IDXSET_FOREACH(o, u->outputs, idx) {
uint32_t r = base_rate;
if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))
continue;
if (o->total_latency < target_latency)
- r -= (uint32_t) ((((double) (target_latency - o->total_latency))/(double)u->adjust_time)*(double)r/PA_USEC_PER_SEC);
+ r -= (uint32_t) ((((double) (target_latency - o->total_latency))/(double)u->adjust_time)*(double)r);
else if (o->total_latency > target_latency)
- r += (uint32_t) ((((double) (o->total_latency - target_latency))/(double)u->adjust_time)*(double)r/PA_USEC_PER_SEC);
+ r += (uint32_t) ((((double) (o->total_latency - target_latency))/(double)u->adjust_time)*(double)r);
if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1)) {
- pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), base_rate, r);
+ pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", o->sink_input->sink->name, base_rate, r);
pa_sink_input_set_rate(o->sink_input, base_rate);
} else {
- pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), r, (double) r / base_rate, (float) o->total_latency);
+ pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", o->sink_input->sink->name, r, (double) r / base_rate, (float) o->total_latency);
pa_sink_input_set_rate(o->sink_input, r);
}
}
@@ -235,7 +245,7 @@ static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct tim
adjust_rates(u);
- pa_core_rttime_restart(u->core, e, pa_rtclock_now() + u->adjust_time * PA_USEC_PER_SEC);
+ pa_core_rttime_restart(u->core, e, pa_rtclock_now() + u->adjust_time);
}
static void process_render_null(struct userdata *u, pa_usec_t now) {
@@ -355,18 +365,15 @@ static void render_memblock(struct userdata *u, struct output *o, size_t length)
u->thread_info.counter += chunk.length;
/* OK, let's send this data to the other threads */
- for (j = u->thread_info.active_outputs; j; j = j->next)
-
- /* Send to other outputs, which are not the requesting
- * one */
+ PA_LLIST_FOREACH(j, u->thread_info.active_outputs) {
+ if (j == o)
+ continue;
- if (j != o)
- pa_asyncmsgq_post(j->inq, PA_MSGOBJECT(j->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, &chunk, NULL);
+ pa_asyncmsgq_post(j->inq, PA_MSGOBJECT(j->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, &chunk, NULL);
+ }
/* And place it directly into the requesting output's queue */
- if (o)
- pa_memblockq_push_align(o->memblockq, &chunk);
-
+ pa_memblockq_push_align(o->memblockq, &chunk);
pa_memblock_unref(chunk.memblock);
}
}
@@ -402,10 +409,18 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
/* If necessary, get some new data */
request_memblock(o, nbytes);
+ /* pa_log("%s q size is %u + %u (%u/%u)", */
+ /* i->sink->name, */
+ /* pa_memblockq_get_nblocks(o->memblockq), */
+ /* pa_memblockq_get_nblocks(i->thread_info.render_memblockq), */
+ /* pa_memblockq_get_maxrewind(o->memblockq), */
+ /* pa_memblockq_get_maxrewind(i->thread_info.render_memblockq)); */
+
if (pa_memblockq_peek(o->memblockq, chunk) < 0)
return -1;
pa_memblockq_drop(o->memblockq, chunk->length);
+
return 0;
}
@@ -440,13 +455,35 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
return;
pa_atomic_store(&o->max_request, (int) nbytes);
-
pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL);
}
+/* Called from thread context */
+static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) {
+ struct output *o;
+ pa_usec_t c;
+
+ pa_assert(i);
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(o = i->userdata);
+
+ c = pa_sink_get_requested_latency_within_thread(i->sink);
+
+ if (c == (pa_usec_t) -1)
+ c = i->sink->thread_info.max_latency;
+
+ if (pa_atomic_load(&o->requested_latency) == (int) c)
+ return;
+
+ pa_atomic_store(&o->requested_latency, (int) c);
+ pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL);
+}
+
/* Called from I/O thread context */
static void sink_input_attach_cb(pa_sink_input *i) {
struct output *o;
+ pa_usec_t c;
pa_sink_input_assert_ref(i);
pa_assert_se(o = i->userdata);
@@ -455,14 +492,24 @@ static void sink_input_attach_cb(pa_sink_input *i) {
pa_assert(!o->inq_rtpoll_item_read && !o->outq_rtpoll_item_write);
o->inq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
- i->sink->rtpoll,
+ i->sink->thread_info.rtpoll,
PA_RTPOLL_LATE, /* This one is not that important, since we check for data in _peek() anyway. */
o->inq);
o->outq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
- i->sink->rtpoll,
+ i->sink->thread_info.rtpoll,
PA_RTPOLL_EARLY,
o->outq);
+
+ pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE);
+
+ pa_atomic_store(&o->max_request, (int) pa_sink_input_get_max_request(i));
+
+ c = pa_sink_get_requested_latency_within_thread(i->sink);
+ pa_atomic_store(&o->requested_latency, (int) (c == (pa_usec_t) -1 ? 0 : c));
+
+ pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL);
+ pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL);
}
/* Called from I/O thread context */
@@ -472,14 +519,15 @@ static void sink_input_detach_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(o = i->userdata);
- /* Shut down the queue from the sink thread to us */
- pa_assert(o->inq_rtpoll_item_read && o->outq_rtpoll_item_write);
-
- pa_rtpoll_item_free(o->inq_rtpoll_item_read);
- o->inq_rtpoll_item_read = NULL;
+ if (o->inq_rtpoll_item_read) {
+ pa_rtpoll_item_free(o->inq_rtpoll_item_read);
+ o->inq_rtpoll_item_read = NULL;
+ }
- pa_rtpoll_item_free(o->outq_rtpoll_item_write);
- o->outq_rtpoll_item_write = NULL;
+ if (o->outq_rtpoll_item_write) {
+ pa_rtpoll_item_free(o->outq_rtpoll_item_write);
+ o->outq_rtpoll_item_write = NULL;
+ }
}
/* Called from main context */
@@ -493,20 +541,6 @@ static void sink_input_kill_cb(pa_sink_input *i) {
output_free(o);
}
-/* Called from IO thread context */
-static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- /* If we are added for the first time, ask for a rewinding so that
- * we are heard right-away. */
- if (PA_SINK_INPUT_IS_LINKED(state) &&
- i->thread_info.state == PA_SINK_INPUT_INIT)
- pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE);
-}
-
/* Called from thread context */
static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct output *o = PA_SINK_INPUT(obj)->userdata;
@@ -537,37 +571,6 @@ static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64
}
/* Called from main context */
-static void disable_output(struct output *o) {
- pa_assert(o);
-
- if (!o->sink_input)
- return;
-
- pa_sink_input_unlink(o->sink_input);
- pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
- pa_sink_input_unref(o->sink_input);
- o->sink_input = NULL;
-}
-
-/* Called from main context */
-static void enable_output(struct output *o) {
- pa_assert(o);
-
- if (o->sink_input)
- return;
-
- if (output_create_sink_input(o) >= 0) {
-
- pa_memblockq_flush_write(o->memblockq);
-
- pa_sink_input_put(o->sink_input);
-
- if (o->userdata->sink && PA_SINK_IS_LINKED(pa_sink_get_state(o->userdata->sink)))
- pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL);
- }
-}
-
-/* Called from main context */
static void suspend(struct userdata *u) {
struct output *o;
uint32_t idx;
@@ -575,8 +578,8 @@ static void suspend(struct userdata *u) {
pa_assert(u);
/* Let's suspend by unlinking all streams */
- for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
- disable_output(o);
+ PA_IDXSET_FOREACH(o, u->outputs, idx)
+ output_disable(o);
pa_log_info("Device suspended...");
}
@@ -589,13 +592,8 @@ static void unsuspend(struct userdata *u) {
pa_assert(u);
/* Let's resume */
- for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
-
- pa_sink_suspend(o->sink, FALSE, PA_SUSPEND_IDLE);
-
- if (PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))
- enable_output(o);
- }
+ PA_IDXSET_FOREACH(o, u->outputs, idx)
+ output_enable(o);
pa_log_info("Resumed successfully...");
}
@@ -639,7 +637,13 @@ static void update_max_request(struct userdata *u) {
size_t max_request = 0;
struct output *o;
- for (o = u->thread_info.active_outputs; o; o = o->next) {
+ pa_assert(u);
+ pa_sink_assert_io_context(u->sink);
+
+ /* Collects the max_request values of all streams and sets the
+ * largest one locally */
+
+ PA_LLIST_FOREACH(o, u->thread_info.active_outputs) {
size_t mr = (size_t) pa_atomic_load(&o->max_request);
if (mr > max_request)
@@ -652,6 +656,67 @@ static void update_max_request(struct userdata *u) {
pa_sink_set_max_request_within_thread(u->sink, max_request);
}
+/* Called from IO context */
+static void update_fixed_latency(struct userdata *u) {
+ pa_usec_t fixed_latency = 0;
+ struct output *o;
+
+ pa_assert(u);
+ pa_sink_assert_io_context(u->sink);
+
+ /* Collects the requested_latency values of all streams and sets
+ * the largest one as fixed_latency locally */
+
+ PA_LLIST_FOREACH(o, u->thread_info.active_outputs) {
+ pa_usec_t rl = (size_t) pa_atomic_load(&o->requested_latency);
+
+ if (rl > fixed_latency)
+ fixed_latency = rl;
+ }
+
+ if (fixed_latency <= 0)
+ fixed_latency = u->block_usec;
+
+ pa_sink_set_fixed_latency_within_thread(u->sink, fixed_latency);
+}
+
+/* Called from thread context of the io thread */
+static void output_add_within_thread(struct output *o) {
+ pa_assert(o);
+ pa_sink_assert_io_context(o->sink);
+
+ PA_LLIST_PREPEND(struct output, o->userdata->thread_info.active_outputs, o);
+
+ pa_assert(!o->outq_rtpoll_item_read && !o->inq_rtpoll_item_write);
+
+ o->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
+ o->userdata->rtpoll,
+ PA_RTPOLL_EARLY-1, /* This item is very important */
+ o->outq);
+ o->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
+ o->userdata->rtpoll,
+ PA_RTPOLL_EARLY,
+ o->inq);
+}
+
+/* Called from thread context of the io thread */
+static void output_remove_within_thread(struct output *o) {
+ pa_assert(o);
+ pa_sink_assert_io_context(o->sink);
+
+ PA_LLIST_REMOVE(struct output, o->userdata->thread_info.active_outputs, o);
+
+ if (o->outq_rtpoll_item_read) {
+ pa_rtpoll_item_free(o->outq_rtpoll_item_read);
+ o->outq_rtpoll_item_read = NULL;
+ }
+
+ if (o->inq_rtpoll_item_write) {
+ pa_rtpoll_item_free(o->inq_rtpoll_item_write);
+ o->inq_rtpoll_item_write = NULL;
+ }
+}
+
/* Called from thread context of the io thread */
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
@@ -684,42 +749,17 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
return 0;
}
- case SINK_MESSAGE_ADD_OUTPUT: {
- struct output *op = data;
-
- PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, op);
-
- pa_assert(!op->outq_rtpoll_item_read && !op->inq_rtpoll_item_write);
-
- op->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
- u->rtpoll,
- PA_RTPOLL_EARLY-1, /* This item is very important */
- op->outq);
- op->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
- u->rtpoll,
- PA_RTPOLL_EARLY,
- op->inq);
-
+ case SINK_MESSAGE_ADD_OUTPUT:
+ output_add_within_thread(data);
update_max_request(u);
+ update_fixed_latency(u);
return 0;
- }
-
- case SINK_MESSAGE_REMOVE_OUTPUT: {
- struct output *op = data;
-
- PA_LLIST_REMOVE(struct output, u->thread_info.active_outputs, op);
-
- pa_assert(op->outq_rtpoll_item_read && op->inq_rtpoll_item_write);
-
- pa_rtpoll_item_free(op->outq_rtpoll_item_read);
- op->outq_rtpoll_item_read = NULL;
-
- pa_rtpoll_item_free(op->inq_rtpoll_item_write);
- op->inq_rtpoll_item_write = NULL;
+ case SINK_MESSAGE_REMOVE_OUTPUT:
+ output_remove_within_thread(data);
update_max_request(u);
+ update_fixed_latency(u);
return 0;
- }
case SINK_MESSAGE_NEED:
render_memblock(u, (struct output*) data, (size_t) offset);
@@ -741,10 +781,13 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
}
case SINK_MESSAGE_UPDATE_MAX_REQUEST:
-
update_max_request(u);
break;
- }
+
+ case SINK_MESSAGE_UPDATE_REQUESTED_LATENCY:
+ update_fixed_latency(u);
+ break;
+}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
@@ -767,7 +810,7 @@ static void update_description(struct userdata *u) {
t = pa_xstrdup("Simultaneous output to");
- for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
+ PA_IDXSET_FOREACH(o, u->outputs, idx) {
char *e;
if (first) {
@@ -801,8 +844,9 @@ static int output_create_sink_input(struct output *o) {
pa_sink_input_new_data_set_channel_map(&data, &o->userdata->sink->channel_map);
data.module = o->userdata->module;
data.resample_method = o->userdata->resample_method;
+ data.flags = PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE|PA_SINK_INPUT_NO_CREATE_ON_SUSPEND;
- pa_sink_input_new(&o->sink_input, o->userdata->core, &data, PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE);
+ pa_sink_input_new(&o->sink_input, o->userdata->core, &data);
pa_sink_input_new_data_done(&data);
@@ -812,9 +856,9 @@ static int output_create_sink_input(struct output *o) {
o->sink_input->parent.process_msg = sink_input_process_msg;
o->sink_input->pop = sink_input_pop_cb;
o->sink_input->process_rewind = sink_input_process_rewind_cb;
- o->sink_input->state_change = sink_input_state_change_cb;
o->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
o->sink_input->update_max_request = sink_input_update_max_request_cb;
+ o->sink_input->update_sink_requested_latency = sink_input_update_sink_requested_latency_cb;
o->sink_input->attach = sink_input_attach_cb;
o->sink_input->detach = sink_input_detach_cb;
o->sink_input->kill = sink_input_kill_cb;
@@ -825,22 +869,19 @@ static int output_create_sink_input(struct output *o) {
return 0;
}
+/* Called from main context */
static struct output *output_new(struct userdata *u, pa_sink *sink) {
struct output *o;
- pa_sink_state_t state;
pa_assert(u);
pa_assert(sink);
pa_assert(u->sink);
- o = pa_xnew(struct output, 1);
+ o = pa_xnew0(struct output, 1);
o->userdata = u;
o->inq = pa_asyncmsgq_new(0);
o->outq = pa_asyncmsgq_new(0);
- o->inq_rtpoll_item_write = o->inq_rtpoll_item_read = NULL;
- o->outq_rtpoll_item_write = o->outq_rtpoll_item_read = NULL;
o->sink = sink;
- o->sink_input = NULL;
o->memblockq = pa_memblockq_new(
0,
MEMBLOCKQ_MAXLENGTH,
@@ -850,84 +891,135 @@ static struct output *output_new(struct userdata *u, pa_sink *sink) {
0,
0,
NULL);
- pa_atomic_store(&o->max_request, 0);
- PA_LLIST_INIT(struct output, o);
pa_assert_se(pa_idxset_put(u->outputs, o, NULL) == 0);
+ update_description(u);
- state = pa_sink_get_state(u->sink);
-
- if (state != PA_SINK_INIT)
- pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL);
- else {
- /* If the sink is not yet started, we need to do the activation ourselves */
- PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, o);
-
- o->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
- u->rtpoll,
- PA_RTPOLL_EARLY-1, /* This item is very important */
- o->outq);
- o->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
- u->rtpoll,
- PA_RTPOLL_EARLY,
- o->inq);
- }
+ return o;
+}
- if (PA_SINK_IS_OPENED(state) || state == PA_SINK_INIT) {
- pa_sink_suspend(sink, FALSE, PA_SUSPEND_IDLE);
+/* Called from main context */
+static void output_free(struct output *o) {
+ pa_assert(o);
- if (PA_SINK_IS_OPENED(pa_sink_get_state(sink)))
- if (output_create_sink_input(o) < 0)
- goto fail;
- }
+ output_disable(o);
- update_description(u);
+ pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL));
+ update_description(o->userdata);
- return o;
+ if (o->inq_rtpoll_item_read)
+ pa_rtpoll_item_free(o->inq_rtpoll_item_read);
+ if (o->inq_rtpoll_item_write)
+ pa_rtpoll_item_free(o->inq_rtpoll_item_write);
-fail:
+ if (o->outq_rtpoll_item_read)
+ pa_rtpoll_item_free(o->outq_rtpoll_item_read);
+ if (o->outq_rtpoll_item_write)
+ pa_rtpoll_item_free(o->outq_rtpoll_item_write);
- if (o) {
- pa_idxset_remove_by_data(u->outputs, o, NULL);
+ if (o->inq)
+ pa_asyncmsgq_unref(o->inq);
- if (o->sink_input) {
- pa_sink_input_unlink(o->sink_input);
- pa_sink_input_unref(o->sink_input);
- }
+ if (o->outq)
+ pa_asyncmsgq_unref(o->outq);
+
+ if (o->memblockq)
+ pa_memblockq_free(o->memblockq);
+
+ pa_xfree(o);
+}
+
+/* Called from main context */
+static void output_enable(struct output *o) {
+ pa_assert(o);
+
+ if (o->sink_input)
+ return;
+
+ /* This might cause the sink to be resumed. The state change hook
+ * of the sink might hence be called from here, which might then
+ * cause us to be called in a loop. Make sure that state changes
+ * for this output don't cause this loop by setting a flag here */
+ o->ignore_state_change = TRUE;
+
+ if (output_create_sink_input(o) >= 0) {
- if (o->memblockq)
- pa_memblockq_free(o->memblockq);
+ if (pa_sink_get_state(o->sink) != PA_SINK_INIT) {
- if (o->inq)
- pa_asyncmsgq_unref(o->inq);
+ /* First we register the output. That means that the sink
+ * will start to pass data to this output. */
+ pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL);
- if (o->outq)
- pa_asyncmsgq_unref(o->outq);
+ /* Then we enable the sink input. That means that the sink
+ * is now asked for new data. */
+ pa_sink_input_put(o->sink_input);
- pa_xfree(o);
+ } else
+ /* Hmm the sink is not yet started, do things right here */
+ output_add_within_thread(o);
}
- return NULL;
+ o->ignore_state_change = FALSE;
}
+/* Called from main context */
+static void output_disable(struct output *o) {
+ pa_assert(o);
+
+ if (!o->sink_input)
+ return;
+
+ /* First we disable the sink input. That means that the sink is
+ * not asked for new data anymore */
+ pa_sink_input_unlink(o->sink_input);
+
+ /* Then we unregister the output. That means that the sink doesn't
+ * pass any further data to this output */
+ pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
+
+ /* Now dellocate the stream */
+ pa_sink_input_unref(o->sink_input);
+ o->sink_input = NULL;
+
+ /* Finally, drop all queued data */
+ pa_memblockq_flush_write(o->memblockq);
+ pa_asyncmsgq_flush(o->inq, FALSE);
+ pa_asyncmsgq_flush(o->outq, FALSE);
+}
+
+/* Called from main context */
+static void output_verify(struct output *o) {
+ pa_assert(o);
+
+ if (PA_SINK_IS_OPENED(pa_sink_get_state(o->userdata->sink)))
+ output_enable(o);
+ else
+ output_disable(o);
+}
+
+/* Called from main context */
static pa_bool_t is_suitable_sink(struct userdata *u, pa_sink *s) {
const char *t;
pa_sink_assert_ref(s);
+ if (s == u->sink)
+ return FALSE;
+
if (!(s->flags & PA_SINK_HARDWARE))
return FALSE;
- if (s == u->sink)
+ if (!(s->flags & PA_SINK_LATENCY))
return FALSE;
if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_CLASS)))
- if (strcmp(t, "sound"))
+ if (!pa_streq(t, "sound"))
return FALSE;
return TRUE;
}
+/* Called from main context */
static pa_hook_result_t sink_put_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) {
struct output *o;
@@ -940,18 +1032,17 @@ static pa_hook_result_t sink_put_hook_cb(pa_core *c, pa_sink *s, struct userdata
return PA_HOOK_OK;
pa_log_info("Configuring new sink: %s", s->name);
-
if (!(o = output_new(u, s))) {
pa_log("Failed to create sink input on sink '%s'.", s->name);
return PA_HOOK_OK;
}
- if (o->sink_input)
- pa_sink_input_put(o->sink_input);
+ output_verify(o);
return PA_HOOK_OK;
}
+/* Called from main context */
static struct output* find_output(struct userdata *u, pa_sink *s) {
struct output *o;
uint32_t idx;
@@ -962,13 +1053,14 @@ static struct output* find_output(struct userdata *u, pa_sink *s) {
if (u->sink == s)
return NULL;
- for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
+ PA_IDXSET_FOREACH(o, u->outputs, idx)
if (o->sink == s)
return o;
return NULL;
}
+/* Called from main context */
static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) {
struct output *o;
@@ -980,26 +1072,25 @@ static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userd
return PA_HOOK_OK;
pa_log_info("Unconfiguring sink: %s", s->name);
-
output_free(o);
return PA_HOOK_OK;
}
+/* Called from main context */
static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) {
struct output *o;
- pa_sink_state_t state;
if (!(o = find_output(u, s)))
return PA_HOOK_OK;
- state = pa_sink_get_state(s);
-
- if (PA_SINK_IS_OPENED(state) && PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input)
- enable_output(o);
+ /* This state change might be triggered because we are creating a
+ * stream here, in that case we don't want to create it a second
+ * time here and enter a loop */
+ if (o->ignore_state_change)
+ return PA_HOOK_OK;
- if (state == PA_SINK_SUSPENDED && o->sink_input)
- disable_output(o);
+ output_verify(o);
return PA_HOOK_OK;
}
@@ -1014,6 +1105,7 @@ int pa__init(pa_module*m) {
struct output *o;
uint32_t idx;
pa_sink_new_data data;
+ uint32_t adjust_time_sec;
pa_assert(m);
@@ -1029,23 +1121,13 @@ int pa__init(pa_module*m) {
}
}
- m->userdata = u = pa_xnew(struct userdata, 1);
+ m->userdata = u = pa_xnew0(struct userdata, 1);
u->core = m->core;
u->module = m;
- u->sink = NULL;
- u->time_event = NULL;
- u->adjust_time = DEFAULT_ADJUST_TIME;
u->rtpoll = pa_rtpoll_new();
pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
- u->thread = NULL;
u->resample_method = resample_method;
u->outputs = pa_idxset_new(NULL, NULL);
- memset(&u->adjust_timestamp, 0, sizeof(u->adjust_timestamp));
- u->sink_put_slot = u->sink_unlink_slot = u->sink_state_changed_slot = NULL;
- PA_LLIST_HEAD_INIT(struct output, u->thread_info.active_outputs);
- pa_atomic_store(&u->thread_info.running, FALSE);
- u->thread_info.in_null_mode = FALSE;
- u->thread_info.counter = 0;
u->thread_info.smoother = pa_smoother_new(
PA_USEC_PER_SEC,
PA_USEC_PER_SEC*2,
@@ -1055,16 +1137,73 @@ int pa__init(pa_module*m) {
0,
FALSE);
- if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) {
+ adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
+ if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) {
pa_log("Failed to parse adjust_time value");
goto fail;
}
+ if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC)
+ u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC;
+ else
+ u->adjust_time = DEFAULT_ADJUST_TIME_USEC;
+
slaves = pa_modargs_get_value(ma, "slaves", NULL);
u->automatic = !slaves;
ss = m->core->default_sample_spec;
map = m->core->default_channel_map;
+
+ /* Check the specified slave sinks for sample_spec and channel_map to use for the combined sink */
+ if (!u->automatic) {
+ const char*split_state = NULL;
+ char *n = NULL;
+ pa_sample_spec slaves_spec;
+ pa_channel_map slaves_map;
+ pa_bool_t is_first_slave = TRUE;
+
+ pa_sample_spec_init(&slaves_spec);
+
+ while ((n = pa_split(slaves, ",", &split_state))) {
+ pa_sink *slave_sink;
+
+ if (!(slave_sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK))) {
+ pa_log("Invalid slave sink '%s'", n);
+ pa_xfree(n);
+ goto fail;
+ }
+
+ pa_xfree(n);
+
+ if (is_first_slave) {
+ slaves_spec = slave_sink->sample_spec;
+ slaves_map = slave_sink->channel_map;
+ is_first_slave = FALSE;
+ } else {
+ if (slaves_spec.format != slave_sink->sample_spec.format)
+ slaves_spec.format = PA_SAMPLE_INVALID;
+
+ if (slaves_spec.rate < slave_sink->sample_spec.rate)
+ slaves_spec.rate = slave_sink->sample_spec.rate;
+
+ if (!pa_channel_map_equal(&slaves_map, &slave_sink->channel_map))
+ slaves_spec.channels = 0;
+ }
+ }
+
+ if (!is_first_slave) {
+ if (slaves_spec.format != PA_SAMPLE_INVALID)
+ ss.format = slaves_spec.format;
+
+ ss.rate = slaves_spec.rate;
+
+ if (slaves_spec.channels > 0) {
+ map = slaves_map;
+ ss.channels = slaves_map.channels;
+ }
+ }
+ }
+
if ((pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0)) {
pa_log("Invalid sample specification.");
goto fail;
@@ -1095,7 +1234,6 @@ int pa__init(pa_module*m) {
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Simultaneous Output");
}
-
u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY);
pa_sink_new_data_done(&data);
@@ -1149,7 +1287,7 @@ int pa__init(pa_module*m) {
/* We're in automatic mode, we add every sink that matches our needs */
- for (s = pa_idxset_first(m->core->sinks, &idx); s; s = pa_idxset_next(m->core->sinks, &idx)) {
+ PA_IDXSET_FOREACH(s, m->core->sinks, idx) {
if (!is_suitable_sink(u, s))
continue;
@@ -1174,12 +1312,11 @@ int pa__init(pa_module*m) {
/* Activate the sink and the sink inputs */
pa_sink_put(u->sink);
- for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
- if (o->sink_input)
- pa_sink_input_put(o->sink_input);
+ PA_IDXSET_FOREACH(o, u->outputs, idx)
+ output_verify(o);
if (u->adjust_time > 0)
- u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time * PA_USEC_PER_SEC, time_callback, u);
+ u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u);
pa_modargs_free(ma);
@@ -1195,37 +1332,6 @@ fail:
return -1;
}
-static void output_free(struct output *o) {
- pa_assert(o);
-
- disable_output(o);
-
- pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL));
-
- update_description(o->userdata);
-
- if (o->inq_rtpoll_item_read)
- pa_rtpoll_item_free(o->inq_rtpoll_item_read);
- if (o->inq_rtpoll_item_write)
- pa_rtpoll_item_free(o->inq_rtpoll_item_write);
-
- if (o->outq_rtpoll_item_read)
- pa_rtpoll_item_free(o->outq_rtpoll_item_read);
- if (o->outq_rtpoll_item_write)
- pa_rtpoll_item_free(o->outq_rtpoll_item_write);
-
- if (o->inq)
- pa_asyncmsgq_unref(o->inq);
-
- if (o->outq)
- pa_asyncmsgq_unref(o->outq);
-
- if (o->memblockq)
- pa_memblockq_free(o->memblockq);
-
- pa_xfree(o);
-}
-
void pa__done(pa_module*m) {
struct userdata *u;
struct output *o;
diff --git a/src/modules/module-console-kit.c b/src/modules/module-console-kit.c
index a666073c..103f5c48 100644
--- a/src/modules/module-console-kit.c
+++ b/src/modules/module-console-kit.c
@@ -187,7 +187,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
}
add_session(u, path);
- return DBUS_HANDLER_RESULT_HANDLED;
} else if (dbus_message_is_signal(message, "org.freedesktop.ConsoleKit.Seat", "SessionRemoved")) {
@@ -202,7 +201,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
}
remove_session(u, path);
- return DBUS_HANDLER_RESULT_HANDLED;
}
finish:
diff --git a/src/modules/module-default-device-restore.c b/src/modules/module-default-device-restore.c
index 27ae60e5..94f589e9 100644
--- a/src/modules/module-default-device-restore.c
+++ b/src/modules/module-default-device-restore.c
@@ -60,7 +60,7 @@ static void load(struct userdata *u) {
if (u->core->default_sink)
pa_log_info("Manually configured default sink, not overwriting.");
- else if ((f = fopen(u->sink_filename, "r"))) {
+ else if ((f = pa_fopen_cloexec(u->sink_filename, "r"))) {
char ln[256] = "";
pa_sink *s;
@@ -81,7 +81,7 @@ static void load(struct userdata *u) {
if (u->core->default_source)
pa_log_info("Manually configured default source, not overwriting.");
- else if ((f = fopen(u->source_filename, "r"))) {
+ else if ((f = pa_fopen_cloexec(u->source_filename, "r"))) {
char ln[256] = "";
pa_source *s;
@@ -108,7 +108,7 @@ static void save(struct userdata *u) {
return;
if (u->sink_filename) {
- if ((f = fopen(u->sink_filename, "w"))) {
+ if ((f = pa_fopen_cloexec(u->sink_filename, "w"))) {
pa_sink *s = pa_namereg_get_default_sink(u->core);
fprintf(f, "%s\n", s ? s->name : "");
fclose(f);
@@ -117,7 +117,7 @@ static void save(struct userdata *u) {
}
if (u->source_filename) {
- if ((f = fopen(u->source_filename, "w"))) {
+ if ((f = pa_fopen_cloexec(u->source_filename, "w"))) {
pa_source *s = pa_namereg_get_default_source(u->core);
fprintf(f, "%s\n", s ? s->name : "");
fclose(f);
diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c
index 18479df3..1fe8eb8b 100644
--- a/src/modules/module-detect.c
+++ b/src/modules/module-detect.c
@@ -50,7 +50,7 @@ PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(TRUE);
PA_MODULE_USAGE("just-one=<boolean>");
-PA_MODULE_DEPRECATED("Please use module-hal-detect instead of module-detect!");
+PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-detect!");
static const char* const valid_modargs[] = {
"just-one",
@@ -63,7 +63,7 @@ static int detect_alsa(pa_core *c, int just_one) {
FILE *f;
int n = 0, n_sink = 0, n_source = 0;
- if (!(f = fopen("/proc/asound/devices", "r"))) {
+ if (!(f = pa_fopen_cloexec("/proc/asound/devices", "r"))) {
if (errno != ENOENT)
pa_log_error("open(\"/proc/asound/devices\") failed: %s", pa_cstrerror(errno));
@@ -119,14 +119,14 @@ static int detect_alsa(pa_core *c, int just_one) {
}
#endif
-#ifdef HAVE_OSS
+#ifdef HAVE_OSS_OUTPUT
static int detect_oss(pa_core *c, int just_one) {
FILE *f;
int n = 0, b = 0;
- if (!(f = fopen("/dev/sndstat", "r")) &&
- !(f = fopen("/proc/sndstat", "r")) &&
- !(f = fopen("/proc/asound/oss/sndstat", "r"))) {
+ if (!(f = pa_fopen_cloexec("/dev/sndstat", "r")) &&
+ !(f = pa_fopen_cloexec("/proc/sndstat", "r")) &&
+ !(f = pa_fopen_cloexec("/proc/asound/oss/sndstat", "r"))) {
if (errno != ENOENT)
pa_log_error("failed to open OSS sndstat device: %s", pa_cstrerror(errno));
@@ -240,7 +240,7 @@ int pa__init(pa_module*m) {
#ifdef HAVE_ALSA
if ((n = detect_alsa(m->core, just_one)) <= 0)
#endif
-#ifdef HAVE_OSS
+#ifdef HAVE_OSS_OUTPUT
if ((n = detect_oss(m->core, just_one)) <= 0)
#endif
#ifdef HAVE_SOLARIS
diff --git a/src/modules/module-device-manager.c b/src/modules/module-device-manager.c
new file mode 100644
index 00000000..3991043d
--- /dev/null
+++ b/src/modules/module-device-manager.c
@@ -0,0 +1,1540 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006-2008 Lennart Poettering
+ Copyright 2009 Colin Guthrie
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/volume.h>
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+#include <pulse/rtclock.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/source-output.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/protocol-native.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/pstream-util.h>
+#include <pulsecore/database.h>
+
+#include "module-device-manager-symdef.h"
+
+PA_MODULE_AUTHOR("Colin Guthrie");
+PA_MODULE_DESCRIPTION("Keep track of devices (and their descriptions) both past and present and prioritise by role");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE(
+ "do_routing=<Automatically route streams based on a priority list (unique per-role)?> "
+ "on_hotplug=<When new device becomes available, recheck streams?> "
+ "on_rescue=<When device becomes unavailable, recheck streams?>");
+
+#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
+#define DUMP_DATABASE
+
+static const char* const valid_modargs[] = {
+ "do_routing",
+ "on_hotplug",
+ "on_rescue",
+ NULL
+};
+
+#define NUM_ROLES 9
+enum {
+ ROLE_NONE,
+ ROLE_VIDEO,
+ ROLE_MUSIC,
+ ROLE_GAME,
+ ROLE_EVENT,
+ ROLE_PHONE,
+ ROLE_ANIMATION,
+ ROLE_PRODUCTION,
+ ROLE_A11Y,
+};
+
+typedef uint32_t role_indexes_t[NUM_ROLES];
+
+static const char* role_names[NUM_ROLES] = {
+ "none",
+ "video",
+ "music",
+ "game",
+ "event",
+ "phone",
+ "animation",
+ "production",
+ "a11y",
+};
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_subscription *subscription;
+ pa_hook_slot
+ *sink_new_hook_slot,
+ *source_new_hook_slot,
+ *sink_input_new_hook_slot,
+ *source_output_new_hook_slot,
+ *sink_put_hook_slot,
+ *source_put_hook_slot,
+ *sink_unlink_hook_slot,
+ *source_unlink_hook_slot,
+ *connection_unlink_hook_slot;
+ pa_time_event *save_time_event;
+ pa_database *database;
+
+ pa_native_protocol *protocol;
+ pa_idxset *subscribed;
+
+ pa_bool_t on_hotplug;
+ pa_bool_t on_rescue;
+ pa_bool_t do_routing;
+
+ role_indexes_t preferred_sinks;
+ role_indexes_t preferred_sources;
+};
+
+#define ENTRY_VERSION 1
+
+struct entry {
+ uint8_t version;
+ char description[PA_NAME_MAX];
+ pa_bool_t user_set_description;
+ char icon[PA_NAME_MAX];
+ role_indexes_t priority;
+} PA_GCC_PACKED;
+
+enum {
+ SUBCOMMAND_TEST,
+ SUBCOMMAND_READ,
+ SUBCOMMAND_RENAME,
+ SUBCOMMAND_DELETE,
+ SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING,
+ SUBCOMMAND_REORDER,
+ SUBCOMMAND_SUBSCRIBE,
+ SUBCOMMAND_EVENT
+};
+
+
+static struct entry* read_entry(struct userdata *u, const char *name) {
+ pa_datum key, data;
+ struct entry *e;
+
+ pa_assert(u);
+ pa_assert(name);
+
+ key.data = (char*) name;
+ key.size = strlen(name);
+
+ pa_zero(data);
+
+ if (!pa_database_get(u->database, &key, &data))
+ goto fail;
+
+ if (data.size != sizeof(struct entry)) {
+ pa_log_debug("Database contains entry for device %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry));
+ goto fail;
+ }
+
+ e = (struct entry*) data.data;
+
+ if (e->version != ENTRY_VERSION) {
+ pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name);
+ goto fail;
+ }
+
+ if (!memchr(e->description, 0, sizeof(e->description))) {
+ pa_log_warn("Database contains entry for device %s with missing NUL byte in description", name);
+ goto fail;
+ }
+
+ if (!memchr(e->icon, 0, sizeof(e->icon))) {
+ pa_log_warn("Database contains entry for device %s with missing NUL byte in icon", name);
+ goto fail;
+ }
+
+ return e;
+
+fail:
+
+ pa_datum_free(&data);
+ return NULL;
+}
+
+#ifdef DUMP_DATABASE
+static void dump_database_helper(struct userdata *u, uint32_t role_index, const char* human, pa_bool_t sink_mode) {
+ pa_assert(u);
+ pa_assert(human);
+
+ if (sink_mode) {
+ pa_sink *s;
+ if (PA_INVALID_INDEX != u->preferred_sinks[role_index] && (s = pa_idxset_get_by_index(u->core->sinks, u->preferred_sinks[role_index])))
+ pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name);
+ else
+ pa_log_debug(" %s No sink specified", human);
+ } else {
+ pa_source *s;
+ if (PA_INVALID_INDEX != u->preferred_sources[role_index] && (s = pa_idxset_get_by_index(u->core->sources, u->preferred_sources[role_index])))
+ pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name);
+ else
+ pa_log_debug(" %s No source specified", human);
+ }
+}
+
+static void dump_database(struct userdata *u) {
+ pa_datum key;
+ pa_bool_t done;
+
+ pa_assert(u);
+
+ done = !pa_database_first(u->database, &key, NULL);
+
+ pa_log_debug("Dumping database");
+ while (!done) {
+ char *name;
+ struct entry *e;
+ pa_datum next_key;
+
+ done = !pa_database_next(u->database, &key, &next_key, NULL);
+
+ name = pa_xstrndup(key.data, key.size);
+
+ if ((e = read_entry(u, name))) {
+ pa_log_debug(" Got entry: %s", name);
+ pa_log_debug(" Description: %s", e->description);
+ pa_log_debug(" Priorities: None: %3u, Video: %3u, Music: %3u, Game: %3u, Event: %3u",
+ e->priority[ROLE_NONE], e->priority[ROLE_VIDEO], e->priority[ROLE_MUSIC], e->priority[ROLE_GAME], e->priority[ROLE_EVENT]);
+ pa_log_debug(" Phone: %3u, Anim: %3u, Prodtn: %3u, A11y: %3u",
+ e->priority[ROLE_PHONE], e->priority[ROLE_ANIMATION], e->priority[ROLE_PRODUCTION], e->priority[ROLE_A11Y]);
+ pa_xfree(e);
+ }
+
+ pa_xfree(name);
+
+ pa_datum_free(&key);
+ key = next_key;
+ }
+
+ if (u->do_routing) {
+ pa_log_debug(" Highest priority devices per-role:");
+
+ pa_log_debug(" Sinks:");
+ for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) {
+ char name[13];
+ uint32_t len = PA_MIN(12u, strlen(role_names[role]));
+ strncpy(name, role_names[role], len);
+ for (int i = len+1; i < 12; ++i) name[i] = ' ';
+ name[len] = ':'; name[0] -= 32; name[12] = '\0';
+ dump_database_helper(u, role, name, TRUE);
+ }
+
+ pa_log_debug(" Sources:");
+ for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) {
+ char name[13];
+ uint32_t len = PA_MIN(12u, strlen(role_names[role]));
+ strncpy(name, role_names[role], len);
+ for (int i = len+1; i < 12; ++i) name[i] = ' ';
+ name[len] = ':'; name[0] -= 32; name[12] = '\0';
+ dump_database_helper(u, role, name, FALSE);
+ }
+ }
+
+ pa_log_debug("Completed database dump");
+}
+#endif
+
+static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(a);
+ pa_assert(e);
+ pa_assert(u);
+
+ pa_assert(e == u->save_time_event);
+ u->core->mainloop->time_free(u->save_time_event);
+ u->save_time_event = NULL;
+
+ pa_database_sync(u->database);
+ pa_log_info("Synced.");
+
+#ifdef DUMP_DATABASE
+ dump_database(u);
+#endif
+}
+
+static void notify_subscribers(struct userdata *u) {
+
+ pa_native_connection *c;
+ uint32_t idx;
+
+ pa_assert(u);
+
+ for (c = pa_idxset_first(u->subscribed, &idx); c; c = pa_idxset_next(u->subscribed, &idx)) {
+ pa_tagstruct *t;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_EXTENSION);
+ pa_tagstruct_putu32(t, 0);
+ pa_tagstruct_putu32(t, u->module->index);
+ pa_tagstruct_puts(t, u->module->name);
+ pa_tagstruct_putu32(t, SUBCOMMAND_EVENT);
+
+ pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), t);
+ }
+}
+
+static void trigger_save(struct userdata *u) {
+
+ pa_assert(u);
+
+ notify_subscribers(u);
+
+ if (u->save_time_event)
+ return;
+
+ u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u);
+}
+
+static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) {
+
+ pa_assert(a);
+ pa_assert(b);
+
+ if (strncmp(a->description, b->description, sizeof(a->description))
+ || a->user_set_description != b->user_set_description
+ || strncmp(a->icon, b->icon, sizeof(a->icon)))
+ return FALSE;
+
+ for (int i=0; i < NUM_ROLES; ++i)
+ if (a->priority[i] != b->priority[i])
+ return FALSE;
+
+ return TRUE;
+}
+
+static char *get_name(const char *key, const char *prefix) {
+ char *t;
+
+ if (strncmp(key, prefix, strlen(prefix)))
+ return NULL;
+
+ t = pa_xstrdup(key + strlen(prefix));
+ return t;
+}
+
+static inline struct entry *load_or_initialize_entry(struct userdata *u, struct entry *entry, const char *name, const char *prefix) {
+ struct entry *old;
+
+ pa_assert(u);
+ pa_assert(entry);
+ pa_assert(name);
+ pa_assert(prefix);
+
+ if ((old = read_entry(u, name)))
+ *entry = *old;
+ else {
+ /* This is a new device, so make sure we write it's priority list correctly */
+ role_indexes_t max_priority;
+ pa_datum key;
+ pa_bool_t done;
+
+ pa_zero(max_priority);
+ done = !pa_database_first(u->database, &key, NULL);
+
+ /* Find all existing devices with the same prefix so we calculate the current max priority for each role */
+ while (!done) {
+ pa_datum next_key;
+
+ done = !pa_database_next(u->database, &key, &next_key, NULL);
+
+ if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) {
+ char *name2;
+ struct entry *e;
+
+ name2 = pa_xstrndup(key.data, key.size);
+
+ if ((e = read_entry(u, name2))) {
+ for (uint32_t i = 0; i < NUM_ROLES; ++i) {
+ max_priority[i] = PA_MAX(max_priority[i], e->priority[i]);
+ }
+
+ pa_xfree(e);
+ }
+
+ pa_xfree(name2);
+ }
+ pa_datum_free(&key);
+ key = next_key;
+ }
+
+ /* Actually initialise our entry now we've calculated it */
+ for (uint32_t i = 0; i < NUM_ROLES; ++i) {
+ entry->priority[i] = max_priority[i] + 1;
+ }
+ entry->user_set_description = FALSE;
+ }
+
+ return old;
+}
+
+static uint32_t get_role_index(const char* role) {
+ pa_assert(role);
+
+ for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i)
+ if (strcmp(role, role_names[i]) == 0)
+ return i;
+
+ return PA_INVALID_INDEX;
+}
+
+static void update_highest_priority_device_indexes(struct userdata *u, const char *prefix, void *ignore_device) {
+ role_indexes_t *indexes, highest_priority_available;
+ pa_datum key;
+ pa_bool_t done, sink_mode;
+
+ pa_assert(u);
+ pa_assert(prefix);
+
+ sink_mode = (strcmp(prefix, "sink:") == 0);
+
+ if (sink_mode)
+ indexes = &u->preferred_sinks;
+ else
+ indexes = &u->preferred_sources;
+
+ for (uint32_t i = 0; i < NUM_ROLES; ++i) {
+ (*indexes)[i] = PA_INVALID_INDEX;
+ }
+ pa_zero(highest_priority_available);
+
+ done = !pa_database_first(u->database, &key, NULL);
+
+ /* Find all existing devices with the same prefix so we find the highest priority device for each role */
+ while (!done) {
+ pa_datum next_key;
+
+ done = !pa_database_next(u->database, &key, &next_key, NULL);
+
+ if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) {
+ char *name, *device_name;
+ struct entry *e;
+
+ name = pa_xstrndup(key.data, key.size);
+ device_name = get_name(name, prefix);
+
+ if ((e = read_entry(u, name))) {
+ for (uint32_t i = 0; i < NUM_ROLES; ++i) {
+ if (!highest_priority_available[i] || e->priority[i] < highest_priority_available[i]) {
+ /* We've found a device with a higher priority than that we've currently got,
+ so see if it is currently available or not and update our list */
+ uint32_t idx;
+ pa_bool_t found = FALSE;
+
+ if (sink_mode) {
+ pa_sink *sink;
+
+ PA_IDXSET_FOREACH(sink, u->core->sinks, idx) {
+ if ((pa_sink*) ignore_device == sink)
+ continue;
+ if (strcmp(sink->name, device_name) == 0) {
+ found = TRUE;
+ idx = sink->index; /* Is this needed? */
+ break;
+ }
+ }
+ } else {
+ pa_source *source;
+
+ PA_IDXSET_FOREACH(source, u->core->sources, idx) {
+ if ((pa_source*) ignore_device == source)
+ continue;
+ if (strcmp(source->name, device_name) == 0) {
+ found = TRUE;
+ idx = source->index; /* Is this needed? */
+ break;
+ }
+ }
+ }
+ if (found) {
+ highest_priority_available[i] = e->priority[i];
+ (*indexes)[i] = idx;
+ }
+
+ }
+ }
+
+ pa_xfree(e);
+ }
+
+ pa_xfree(name);
+ pa_xfree(device_name);
+ }
+
+ pa_datum_free(&key);
+ key = next_key;
+ }
+}
+
+
+static void route_sink_input(struct userdata *u, pa_sink_input *si) {
+ const char *role;
+ uint32_t role_index, device_index;
+ pa_sink *sink;
+
+ pa_assert(u);
+ pa_assert(u->do_routing);
+
+ if (si->save_sink)
+ return;
+
+ /* Skip this if it is already in the process of being moved anyway */
+ if (!si->sink)
+ return;
+
+ /* It might happen that a stream and a sink are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si)))
+ return;
+
+ if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE)))
+ role_index = get_role_index("none");
+ else
+ role_index = get_role_index(role);
+
+ if (PA_INVALID_INDEX == role_index)
+ return;
+
+ device_index = u->preferred_sinks[role_index];
+ if (PA_INVALID_INDEX == device_index)
+ return;
+
+ if (!(sink = pa_idxset_get_by_index(u->core->sinks, device_index)))
+ return;
+
+ if (si->sink != sink)
+ pa_sink_input_move_to(si, sink, FALSE);
+}
+
+static pa_hook_result_t route_sink_inputs(struct userdata *u, pa_sink *ignore_sink) {
+ pa_sink_input *si;
+ uint32_t idx;
+
+ pa_assert(u);
+
+ if (!u->do_routing)
+ return PA_HOOK_OK;
+
+ update_highest_priority_device_indexes(u, "sink:", ignore_sink);
+
+ PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) {
+ route_sink_input(u, si);
+ }
+
+ return PA_HOOK_OK;
+}
+
+static void route_source_output(struct userdata *u, pa_source_output *so) {
+ const char *role;
+ uint32_t role_index, device_index;
+ pa_source *source;
+
+ pa_assert(u);
+ pa_assert(u->do_routing);
+
+ if (so->save_source)
+ return;
+
+ if (so->direct_on_input)
+ return;
+
+ /* Skip this if it is already in the process of being moved anyway */
+ if (!so->source)
+ return;
+
+ /* It might happen that a stream and a source are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so)))
+ return;
+
+ if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE)))
+ role_index = get_role_index("none");
+ else
+ role_index = get_role_index(role);
+
+ if (PA_INVALID_INDEX == role_index)
+ return;
+
+ device_index = u->preferred_sources[role_index];
+ if (PA_INVALID_INDEX == device_index)
+ return;
+
+ if (!(source = pa_idxset_get_by_index(u->core->sources, device_index)))
+ return;
+
+ if (so->source != source)
+ pa_source_output_move_to(so, source, FALSE);
+}
+
+static pa_hook_result_t route_source_outputs(struct userdata *u, pa_source* ignore_source) {
+ pa_source_output *so;
+ uint32_t idx;
+
+ pa_assert(u);
+
+ if (!u->do_routing)
+ return PA_HOOK_OK;
+
+ update_highest_priority_device_indexes(u, "source:", ignore_source);
+
+ PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) {
+ route_source_output(u, so);
+ }
+
+ return PA_HOOK_OK;
+}
+
+static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ struct userdata *u = userdata;
+ struct entry entry, *old = NULL;
+ char *name = NULL;
+ pa_datum key, data;
+
+ pa_assert(c);
+ pa_assert(u);
+
+ if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) &&
+ t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) &&
+ t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW) &&
+ t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE) &&
+
+ /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
+ t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) &&
+ /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
+ t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE))
+ return;
+
+ pa_zero(entry);
+ entry.version = ENTRY_VERSION;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
+ pa_sink_input *si;
+
+ if (!u->do_routing)
+ return;
+ if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
+ return;
+
+ /* The role may change mid-stream, so we reroute */
+ route_sink_input(u, si);
+
+ return;
+ } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) {
+ pa_source_output *so;
+
+ if (!u->do_routing)
+ return;
+ if (!(so = pa_idxset_get_by_index(c->source_outputs, idx)))
+ return;
+
+ /* The role may change mid-stream, so we reroute */
+ route_source_output(u, so);
+
+ return;
+ } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) {
+ pa_sink *sink;
+
+ if (!(sink = pa_idxset_get_by_index(c->sinks, idx)))
+ return;
+
+ name = pa_sprintf_malloc("sink:%s", sink->name);
+
+ old = load_or_initialize_entry(u, &entry, name, "sink:");
+
+ if (!entry.user_set_description)
+ pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description));
+ else if (strncmp(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)) != 0) {
+ /* Warning: If two modules fight over the description, this could cause an infinite loop.
+ by changing the description here, we retrigger this subscription callback. The only thing stopping us from
+ looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
+ the description, this will fail... */
+ pa_sink_set_description(sink, entry.description);
+ }
+
+ pa_strlcpy(entry.icon, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME)), sizeof(entry.icon));
+
+ } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) {
+ pa_source *source;
+
+ pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
+
+ if (!(source = pa_idxset_get_by_index(c->sources, idx)))
+ return;
+
+ if (source->monitor_of)
+ return;
+
+ name = pa_sprintf_malloc("source:%s", source->name);
+
+ old = load_or_initialize_entry(u, &entry, name, "source:");
+
+ if (!entry.user_set_description)
+ pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description));
+ else if (strncmp(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)) != 0) {
+ /* Warning: If two modules fight over the description, this could cause an infinite loop.
+ by changing the description here, we retrigger this subscription callback. The only thing stopping us from
+ looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
+ the description, this will fail... */
+ pa_source_set_description(source, entry.description);
+ }
+
+ pa_strlcpy(entry.icon, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME)), sizeof(entry.icon));
+ }
+
+ pa_assert(name);
+
+ if (old) {
+
+ if (entries_equal(old, &entry)) {
+ pa_xfree(old);
+ pa_xfree(name);
+
+ return;
+ }
+
+ pa_xfree(old);
+ }
+
+ key.data = name;
+ key.size = strlen(name);
+
+ data.data = &entry;
+ data.size = sizeof(entry);
+
+ pa_log_info("Storing device %s.", name);
+
+ if (pa_database_set(u->database, &key, &data, TRUE) == 0)
+ trigger_save(u);
+ else
+ pa_log_warn("Could not save device");;
+
+ pa_xfree(name);
+}
+
+static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) {
+ char *name;
+ struct entry *e;
+
+ pa_assert(c);
+ pa_assert(new_data);
+ pa_assert(u);
+
+ name = pa_sprintf_malloc("sink:%s", new_data->name);
+
+ if ((e = read_entry(u, name))) {
+ if (e->user_set_description && strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) {
+ pa_log_info("Restoring description for sink %s.", new_data->name);
+ pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description);
+ }
+
+ pa_xfree(e);
+ }
+
+ pa_xfree(name);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) {
+ char *name;
+ struct entry *e;
+
+ pa_assert(c);
+ pa_assert(new_data);
+ pa_assert(u);
+
+ name = pa_sprintf_malloc("source:%s", new_data->name);
+
+ if ((e = read_entry(u, name))) {
+ if (e->user_set_description && strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) {
+ /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */
+ pa_log_info("Restoring description for source %s.", new_data->name);
+ pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description);
+ }
+
+ pa_xfree(e);
+ }
+
+ pa_xfree(name);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) {
+ const char *role;
+ uint32_t role_index;
+
+ pa_assert(c);
+ pa_assert(new_data);
+ pa_assert(u);
+
+ if (!u->do_routing)
+ return PA_HOOK_OK;
+
+ if (new_data->sink)
+ pa_log_debug("Not restoring device for stream because already set.");
+ else {
+ if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE)))
+ role_index = get_role_index("none");
+ else
+ role_index = get_role_index(role);
+
+ if (PA_INVALID_INDEX != role_index) {
+ uint32_t device_index;
+
+ device_index = u->preferred_sinks[role_index];
+ if (PA_INVALID_INDEX != device_index) {
+ pa_sink *sink;
+
+ if ((sink = pa_idxset_get_by_index(u->core->sinks, device_index))) {
+ new_data->sink = sink;
+ new_data->save_sink = FALSE;
+ }
+ }
+ }
+ }
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) {
+ const char *role;
+ uint32_t role_index;
+
+ pa_assert(c);
+ pa_assert(new_data);
+ pa_assert(u);
+
+ if (!u->do_routing)
+ return PA_HOOK_OK;
+
+ if (new_data->direct_on_input)
+ return PA_HOOK_OK;
+
+ if (new_data->source)
+ pa_log_debug("Not restoring device for stream because already set.");
+ else {
+ if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE)))
+ role_index = get_role_index("none");
+ else
+ role_index = get_role_index(role);
+
+ if (PA_INVALID_INDEX != role_index) {
+ uint32_t device_index;
+
+ device_index = u->preferred_sources[role_index];
+ if (PA_INVALID_INDEX != device_index) {
+ pa_source *source;
+
+ if ((source = pa_idxset_get_by_index(u->core->sources, device_index))) {
+ new_data->source = source;
+ new_data->save_source = FALSE;
+ }
+ }
+ }
+ }
+
+ return PA_HOOK_OK;
+}
+
+
+static pa_hook_result_t sink_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_sink *sink, struct userdata *u) {
+ pa_assert(c);
+ pa_assert(u);
+ pa_assert(u->core == c);
+ pa_assert(u->on_hotplug);
+
+ notify_subscribers(u);
+
+ return route_sink_inputs(u, NULL);
+}
+
+static pa_hook_result_t source_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_source *source, struct userdata *u) {
+ pa_assert(c);
+ pa_assert(u);
+ pa_assert(u->core == c);
+ pa_assert(u->on_hotplug);
+
+ notify_subscribers(u);
+
+ return route_source_outputs(u, NULL);
+}
+
+static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) {
+ pa_assert(c);
+ pa_assert(sink);
+ pa_assert(u);
+ pa_assert(u->core == c);
+ pa_assert(u->on_rescue);
+
+ /* There's no point in doing anything if the core is shut down anyway */
+ if (c->state == PA_CORE_SHUTDOWN)
+ return PA_HOOK_OK;
+
+ notify_subscribers(u);
+
+ return route_sink_inputs(u, sink);
+}
+
+static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) {
+ pa_assert(c);
+ pa_assert(source);
+ pa_assert(u);
+ pa_assert(u->core == c);
+ pa_assert(u->on_rescue);
+
+ /* There's no point in doing anything if the core is shut down anyway */
+ if (c->state == PA_CORE_SHUTDOWN)
+ return PA_HOOK_OK;
+
+ notify_subscribers(u);
+
+ return route_source_outputs(u, source);
+}
+
+
+static void apply_entry(struct userdata *u, const char *name, struct entry *e) {
+ uint32_t idx;
+ char *n;
+
+ pa_assert(u);
+ pa_assert(name);
+ pa_assert(e);
+
+ if (!e->user_set_description)
+ return;
+
+ if ((n = get_name(name, "sink:"))) {
+ pa_sink *s;
+ PA_IDXSET_FOREACH(s, u->core->sinks, idx) {
+ if (!pa_streq(s->name, n)) {
+ continue;
+ }
+
+ pa_log_info("Setting description for sink %s to '%s'", s->name, e->description);
+ pa_sink_set_description(s, e->description);
+ }
+ pa_xfree(n);
+ }
+ else if ((n = get_name(name, "source:"))) {
+ pa_source *s;
+ PA_IDXSET_FOREACH(s, u->core->sources, idx) {
+ if (!pa_streq(s->name, n)) {
+ continue;
+ }
+
+ if (s->monitor_of) {
+ pa_log_warn("Cowardly refusing to set the description for monitor source %s.", s->name);
+ continue;
+ }
+
+ pa_log_info("Setting description for source %s to '%s'", s->name, e->description);
+ pa_source_set_description(s, e->description);
+ }
+ pa_xfree(n);
+ }
+}
+
+
+#define EXT_VERSION 1
+
+static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) {
+ struct userdata *u;
+ uint32_t command;
+ pa_tagstruct *reply = NULL;
+
+ pa_assert(p);
+ pa_assert(m);
+ pa_assert(c);
+ pa_assert(t);
+
+ u = m->userdata;
+
+ if (pa_tagstruct_getu32(t, &command) < 0)
+ goto fail;
+
+ reply = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(reply, PA_COMMAND_REPLY);
+ pa_tagstruct_putu32(reply, tag);
+
+ switch (command) {
+ case SUBCOMMAND_TEST: {
+ if (!pa_tagstruct_eof(t))
+ goto fail;
+
+ pa_tagstruct_putu32(reply, EXT_VERSION);
+ break;
+ }
+
+ case SUBCOMMAND_READ: {
+ pa_datum key;
+ pa_bool_t done;
+
+ if (!pa_tagstruct_eof(t))
+ goto fail;
+
+ done = !pa_database_first(u->database, &key, NULL);
+
+ while (!done) {
+ pa_datum next_key;
+ struct entry *e;
+ char *name;
+
+ done = !pa_database_next(u->database, &key, &next_key, NULL);
+
+ name = pa_xstrndup(key.data, key.size);
+ pa_datum_free(&key);
+
+ if ((e = read_entry(u, name))) {
+ uint32_t idx;
+ char *devname;
+ uint32_t found_index = PA_INVALID_INDEX;
+
+ if ((devname = get_name(name, "sink:"))) {
+ pa_sink* s;
+ PA_IDXSET_FOREACH(s, u->core->sinks, idx) {
+ if (strcmp(s->name, devname) == 0) {
+ found_index = s->index;
+ break;
+ }
+ }
+ pa_xfree(devname);
+ } else if ((devname = get_name(name, "source:"))) {
+ pa_source* s;
+ PA_IDXSET_FOREACH(s, u->core->sources, idx) {
+ if (strcmp(s->name, devname) == 0) {
+ found_index = s->index;
+ break;
+ }
+ }
+ pa_xfree(devname);
+ }
+
+ pa_tagstruct_puts(reply, name);
+ pa_tagstruct_puts(reply, e->description);
+ pa_tagstruct_puts(reply, e->icon);
+ pa_tagstruct_putu32(reply, found_index);
+ pa_tagstruct_putu32(reply, NUM_ROLES);
+
+ for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) {
+ pa_tagstruct_puts(reply, role_names[i]);
+ pa_tagstruct_putu32(reply, e->priority[i]);
+ }
+
+ pa_xfree(e);
+ }
+
+ pa_xfree(name);
+
+ key = next_key;
+ }
+
+ break;
+ }
+
+ case SUBCOMMAND_RENAME: {
+
+ struct entry *e;
+ const char *device, *description;
+
+ if (pa_tagstruct_gets(t, &device) < 0 ||
+ pa_tagstruct_gets(t, &description) < 0)
+ goto fail;
+
+ if (!device || !*device || !description || !*description)
+ goto fail;
+
+ if ((e = read_entry(u, device))) {
+ pa_datum key, data;
+
+ pa_strlcpy(e->description, description, sizeof(e->description));
+ e->user_set_description = TRUE;
+
+ key.data = (char *) device;
+ key.size = strlen(device);
+
+ data.data = e;
+ data.size = sizeof(*e);
+
+ if (pa_database_set(u->database, &key, &data, TRUE) == 0) {
+ apply_entry(u, device, e);
+
+ trigger_save(u);
+ }
+ else
+ pa_log_warn("Could not save device");
+
+ pa_xfree(e);
+ }
+ else
+ pa_log_warn("Could not rename device %s, no entry in database", device);
+
+ break;
+ }
+
+ case SUBCOMMAND_DELETE:
+
+ while (!pa_tagstruct_eof(t)) {
+ const char *name;
+ pa_datum key;
+
+ if (pa_tagstruct_gets(t, &name) < 0)
+ goto fail;
+
+ key.data = (char*) name;
+ key.size = strlen(name);
+
+ /** @todo: Reindex the priorities */
+ pa_database_unset(u->database, &key);
+ }
+
+ trigger_save(u);
+
+ break;
+
+ case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING: {
+
+ pa_bool_t enable;
+
+ if (pa_tagstruct_get_boolean(t, &enable) < 0)
+ goto fail;
+
+ if ((u->do_routing = enable)) {
+ /* Update our caches */
+ update_highest_priority_device_indexes(u, "sink:", NULL);
+ update_highest_priority_device_indexes(u, "source:", NULL);
+ }
+
+ break;
+ }
+
+ case SUBCOMMAND_REORDER: {
+
+ const char *role;
+ struct entry *e;
+ uint32_t role_index, n_devices;
+ pa_datum key, data;
+ pa_bool_t done, sink_mode = TRUE;
+ struct device_t { uint32_t prio; char *device; };
+ struct device_t *device;
+ struct device_t **devices;
+ uint32_t i, idx, offset;
+ pa_hashmap *h;
+ /*void *state;*/
+ pa_bool_t first;
+
+ if (pa_tagstruct_gets(t, &role) < 0 ||
+ pa_tagstruct_getu32(t, &n_devices) < 0 ||
+ n_devices < 1)
+ goto fail;
+
+ if (PA_INVALID_INDEX == (role_index = get_role_index(role)))
+ goto fail;
+
+ /* Cycle through the devices given and make sure they exist */
+ h = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ first = TRUE;
+ idx = 0;
+ for (i = 0; i < n_devices; ++i) {
+ const char *s;
+ if (pa_tagstruct_gets(t, &s) < 0) {
+ while ((device = pa_hashmap_steal_first(h))) {
+ pa_xfree(device->device);
+ pa_xfree(device);
+ }
+
+ pa_hashmap_free(h, NULL, NULL);
+ pa_log_error("Protocol error on reorder");
+ goto fail;
+ }
+
+ /* Ensure this is a valid entry */
+ if (!(e = read_entry(u, s))) {
+ while ((device = pa_hashmap_steal_first(h))) {
+ pa_xfree(device->device);
+ pa_xfree(device);
+ }
+
+ pa_hashmap_free(h, NULL, NULL);
+ pa_log_error("Client specified an unknown device in it's reorder list.");
+ goto fail;
+ }
+ pa_xfree(e);
+
+ if (first) {
+ first = FALSE;
+ sink_mode = (0 == strncmp("sink:", s, 5));
+ } else if ((sink_mode && 0 != strncmp("sink:", s, 5))
+ || (!sink_mode && 0 != strncmp("source:", s, 7)))
+ {
+ while ((device = pa_hashmap_steal_first(h))) {
+ pa_xfree(device->device);
+ pa_xfree(device);
+ }
+
+ pa_hashmap_free(h, NULL, NULL);
+ pa_log_error("Attempted to reorder mixed devices (sinks and sources)");
+ goto fail;
+ }
+
+ /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */
+ device = pa_xnew(struct device_t, 1);
+ device->device = pa_xstrdup(s);
+ if (pa_hashmap_put(h, device->device, device) == 0) {
+ device->prio = idx;
+ idx++;
+ } else {
+ pa_xfree(device->device);
+ pa_xfree(device);
+ }
+ }
+
+ /*pa_log_debug("Hashmap contents (received from client)");
+ PA_HASHMAP_FOREACH(device, h, state) {
+ pa_log_debug(" - %s (%d)", device->device, device->prio);
+ }*/
+
+ /* Now cycle through our list and add all the devices.
+ This has the effect of addign in any in our DB,
+ not specified in the device list (and thus will be
+ tacked on at the end) */
+ offset = idx;
+ done = !pa_database_first(u->database, &key, NULL);
+
+ while (!done && idx < 256) {
+ pa_datum next_key;
+
+ done = !pa_database_next(u->database, &key, &next_key, NULL);
+
+ device = pa_xnew(struct device_t, 1);
+ device->device = pa_xstrndup(key.data, key.size);
+ if ((sink_mode && 0 == strncmp("sink:", device->device, 5))
+ || (!sink_mode && 0 == strncmp("source:", device->device, 7))) {
+
+ /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */
+ if (pa_hashmap_put(h, device->device, device) == 0
+ && (e = read_entry(u, device->device))) {
+ /* We add offset on to the existing priorirty so that when we order, the
+ existing entries are always lower priority than the new ones. */
+ device->prio = (offset + e->priority[role_index]);
+ pa_xfree(e);
+ }
+ else {
+ pa_xfree(device->device);
+ pa_xfree(device);
+ }
+ } else {
+ pa_xfree(device->device);
+ pa_xfree(device);
+ }
+
+ pa_datum_free(&key);
+
+ key = next_key;
+ }
+
+ /*pa_log_debug("Hashmap contents (combined with database)");
+ PA_HASHMAP_FOREACH(device, h, state) {
+ pa_log_debug(" - %s (%d)", device->device, device->prio);
+ }*/
+
+ /* Now we put all the entries in a simple list for sorting it. */
+ n_devices = pa_hashmap_size(h);
+ devices = pa_xnew(struct device_t *, n_devices);
+ idx = 0;
+ while ((device = pa_hashmap_steal_first(h))) {
+ devices[idx++] = device;
+ }
+ pa_hashmap_free(h, NULL, NULL);
+
+ /* Simple bubble sort */
+ for (i = 0; i < n_devices; ++i) {
+ for (uint32_t j = i; j < n_devices; ++j) {
+ if (devices[i]->prio > devices[j]->prio) {
+ struct device_t *tmp;
+ tmp = devices[i];
+ devices[i] = devices[j];
+ devices[j] = tmp;
+ }
+ }
+ }
+
+ /*pa_log_debug("Sorted device list");
+ for (i = 0; i < n_devices; ++i) {
+ pa_log_debug(" - %s (%d)", devices[i]->device, devices[i]->prio);
+ }*/
+
+ /* Go through in order and write the new entry and cleanup our own list */
+ idx = 1;
+ first = TRUE;
+ for (i = 0; i < n_devices; ++i) {
+ if ((e = read_entry(u, devices[i]->device))) {
+ if (e->priority[role_index] == idx)
+ idx++;
+ else {
+ e->priority[role_index] = idx;
+
+ key.data = (char *) devices[i]->device;
+ key.size = strlen(devices[i]->device);
+
+ data.data = e;
+ data.size = sizeof(*e);
+
+ if (pa_database_set(u->database, &key, &data, TRUE) == 0) {
+ first = FALSE;
+ idx++;
+ }
+ }
+
+ pa_xfree(e);
+ }
+ pa_xfree(devices[i]->device);
+ pa_xfree(devices[i]);
+ }
+
+ if (!first) {
+ trigger_save(u);
+
+ if (sink_mode)
+ route_sink_inputs(u, NULL);
+ else
+ route_source_outputs(u, NULL);
+ }
+
+ break;
+ }
+
+ case SUBCOMMAND_SUBSCRIBE: {
+
+ pa_bool_t enabled;
+
+ if (pa_tagstruct_get_boolean(t, &enabled) < 0 ||
+ !pa_tagstruct_eof(t))
+ goto fail;
+
+ if (enabled)
+ pa_idxset_put(u->subscribed, c, NULL);
+ else
+ pa_idxset_remove_by_data(u->subscribed, c, NULL);
+
+ break;
+ }
+
+ default:
+ goto fail;
+ }
+
+ pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply);
+ return 0;
+
+ fail:
+
+ if (reply)
+ pa_tagstruct_free(reply);
+
+ return -1;
+}
+
+static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_native_connection *c, struct userdata *u) {
+ pa_assert(p);
+ pa_assert(c);
+ pa_assert(u);
+
+ pa_idxset_remove_by_data(u->subscribed, c, NULL);
+ return PA_HOOK_OK;
+}
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ char *fname;
+ pa_sink *sink;
+ pa_source *source;
+ uint32_t idx;
+ pa_bool_t do_routing = FALSE, on_hotplug = TRUE, on_rescue = TRUE;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "do_routing", &do_routing) < 0 ||
+ pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 ||
+ pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) {
+ pa_log("on_hotplug= and on_rescue= expect boolean arguments");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ u->do_routing = do_routing;
+ u->on_hotplug = on_hotplug;
+ u->on_rescue = on_rescue;
+ u->subscribed = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+ u->protocol = pa_native_protocol_get(m->core);
+ pa_native_protocol_install_ext(u->protocol, m, extension_cb);
+
+ u->connection_unlink_hook_slot = pa_hook_connect(&pa_native_protocol_hooks(u->protocol)[PA_NATIVE_HOOK_CONNECTION_UNLINK], PA_HOOK_NORMAL, (pa_hook_cb_t) connection_unlink_hook_cb, u);
+
+ u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u);
+
+ /* Used to handle device description management */
+ u->sink_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_new_hook_callback, u);
+ u->source_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_new_hook_callback, u);
+
+ /* The following slots are used to deal with routing */
+ /* A little bit later than module-stream-restore, but before module-intended-roles */
+ u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+5, (pa_hook_cb_t) sink_input_new_hook_callback, u);
+ u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY+5, (pa_hook_cb_t) source_output_new_hook_callback, u);
+
+ if (on_hotplug) {
+ /* A little bit later than module-stream-restore, but before module-intended-roles */
+ u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+5, (pa_hook_cb_t) sink_put_hook_callback, u);
+ u->source_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+5, (pa_hook_cb_t) source_put_hook_callback, u);
+ }
+
+ if (on_rescue) {
+ /* A little bit later than module-stream-restore, a little bit earlier than module-intended-roles, module-rescue-streams, ... */
+ u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) sink_unlink_hook_callback, u);
+ u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) source_unlink_hook_callback, u);
+ }
+
+ if (!(fname = pa_state_path("device-manager", TRUE)))
+ goto fail;
+
+ if (!(u->database = pa_database_open(fname, TRUE))) {
+ pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno));
+ pa_xfree(fname);
+ goto fail;
+ }
+
+ pa_log_info("Sucessfully opened database file '%s'.", fname);
+ pa_xfree(fname);
+
+ /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */
+ PA_IDXSET_FOREACH(sink, m->core->sinks, idx)
+ subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u);
+
+ PA_IDXSET_FOREACH(source, m->core->sources, idx)
+ subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u);
+
+ /* Perform the routing (if it's enabled) which will update our priority list cache too */
+ for (uint32_t i = 0; i < NUM_ROLES; ++i) {
+ u->preferred_sinks[i] = u->preferred_sources[i] = PA_INVALID_INDEX;
+ }
+
+ route_sink_inputs(u, NULL);
+ route_source_outputs(u, NULL);
+
+#ifdef DUMP_DATABASE
+ dump_database(u);
+#endif
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ pa__done(m);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata* u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->subscription)
+ pa_subscription_free(u->subscription);
+
+ if (u->sink_new_hook_slot)
+ pa_hook_slot_free(u->sink_new_hook_slot);
+ if (u->source_new_hook_slot)
+ pa_hook_slot_free(u->source_new_hook_slot);
+
+ if (u->sink_input_new_hook_slot)
+ pa_hook_slot_free(u->sink_input_new_hook_slot);
+ if (u->source_output_new_hook_slot)
+ pa_hook_slot_free(u->source_output_new_hook_slot);
+
+ if (u->sink_put_hook_slot)
+ pa_hook_slot_free(u->sink_put_hook_slot);
+ if (u->source_put_hook_slot)
+ pa_hook_slot_free(u->source_put_hook_slot);
+
+ if (u->sink_unlink_hook_slot)
+ pa_hook_slot_free(u->sink_unlink_hook_slot);
+ if (u->source_unlink_hook_slot)
+ pa_hook_slot_free(u->source_unlink_hook_slot);
+
+ if (u->save_time_event)
+ u->core->mainloop->time_free(u->save_time_event);
+
+ if (u->database)
+ pa_database_close(u->database);
+
+ if (u->protocol) {
+ pa_native_protocol_remove_ext(u->protocol, m);
+ pa_native_protocol_unref(u->protocol);
+ }
+
+ if (u->subscribed)
+ pa_idxset_free(u->subscribed, NULL, NULL);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c
index 120b762c..da6c9666 100644
--- a/src/modules/module-device-restore.c
+++ b/src/modules/module-device-restore.c
@@ -218,7 +218,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
if (sink->save_volume) {
entry.channel_map = sink->channel_map;
- entry.volume = *pa_sink_get_volume(sink, FALSE, TRUE);
+ entry.volume = *pa_sink_get_volume(sink, FALSE);
entry.volume_valid = TRUE;
}
diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c
new file mode 100644
index 00000000..7c0ccd3a
--- /dev/null
+++ b/src/modules/module-equalizer-sink.c
@@ -0,0 +1,2161 @@
+/***
+This file is part of PulseAudio.
+
+This module is based off Lennart Poettering's LADSPA sink and swaps out
+LADSPA functionality for a dbus-aware STFT OLA based digital equalizer.
+All new work is published under Pulseaudio's original license.
+Copyright 2009 Jason Newton <nevion@gmail.com>
+
+Original Author:
+Copyright 2004-2008 Lennart Poettering
+
+PulseAudio is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published
+by the Free Software Foundation; either version 2.1 of the License,
+or (at your option) any later version.
+
+PulseAudio is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with PulseAudio; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <float.h>
+#include <math.h>
+#include <fftw3.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/i18n.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/aupdate.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/strlist.h>
+#include <pulsecore/database.h>
+#include <pulsecore/protocol-dbus.h>
+#include <pulsecore/dbus-util.h>
+
+#include <stdint.h>
+#include <time.h>
+
+
+//#undef __SSE2__
+#ifdef __SSE2__
+#include <xmmintrin.h>
+#include <emmintrin.h>
+#endif
+
+
+
+#include "module-equalizer-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Jason Newton");
+PA_MODULE_DESCRIPTION(_("General Purpose Equalizer"));
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(_("sink=<sink to connect to> "));
+
+#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
+
+
+struct userdata {
+ pa_module *module;
+ pa_sink *sink;
+ pa_sink_input *sink_input;
+ char *name;
+
+ size_t channels;
+ size_t fft_size;//length (res) of fft
+ size_t window_size;/*
+ *sliding window size
+ *effectively chooses R
+ */
+ size_t R;/* the hop size between overlapping windows
+ * the latency of the filter, calculated from window_size
+ * based on constraints of COLA and window function
+ */
+ //for twiddling with pulseaudio
+ size_t overlap_size;//window_size-R
+ size_t samples_gathered;
+ size_t input_buffer_max;
+ //message
+ float *W;//windowing function (time domain)
+ float *work_buffer, **input, **overlap_accum;
+ fftwf_complex *output_window;
+ fftwf_plan forward_plan, inverse_plan;
+ //size_t samplings;
+
+ float **Xs;
+ float ***Hs;//thread updatable copies of the freq response filters (magintude based)
+ pa_aupdate **a_H;
+ pa_memchunk conv_buffer;
+ pa_memblockq *input_q;
+ pa_bool_t first_iteration;
+
+ pa_dbus_protocol *dbus_protocol;
+ char *dbus_path;
+ pa_bool_t set_default;
+
+ pa_database *database;
+ char **base_profiles;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "sink_properties",
+ "master",
+ "format",
+ "rate",
+ "set_default",
+ "channels",
+ "channel_map",
+ NULL
+};
+
+
+#define v_size 4
+#define SINKLIST "equalized_sinklist"
+#define EQDB "equalizer_db"
+#define EQ_STATE_DB "equalizer-state"
+#define FILTER_SIZE (u->fft_size / 2 + 1)
+#define CHANNEL_PROFILE_SIZE (FILTER_SIZE + 1)
+#define FILTER_STATE_SIZE (CHANNEL_PROFILE_SIZE * u->channels)
+static void dbus_init(struct userdata *u);
+static void dbus_done(struct userdata *u);
+
+static void hanning_window(float *W, size_t window_size){
+ //h=.5*(1-cos(2*pi*j/(window_size+1)), COLA for R=(M+1)/2
+ for(size_t i=0; i < window_size;++i){
+ W[i] = (float).5*(1-cos(2*M_PI*i/(window_size+1)));
+ }
+}
+
+static void fix_filter(float *H, size_t fft_size){
+ //divide out the fft gain
+ for(size_t i = 0; i < fft_size / 2 + 1; ++i){
+ H[i] /= fft_size;
+ }
+}
+
+static void interpolate(float *signal, size_t length, uint32_t *xs, float *ys, size_t n_points){
+ //Note that xs must be monotonically increasing!
+ float x_range_lower, x_range_upper, c0;
+ pa_assert_se(n_points>=2);
+ pa_assert_se(xs[0] == 0);
+ pa_assert_se(xs[n_points - 1] == length - 1);
+ for(size_t x = 0, x_range_lower_i = 0; x < length-1; ++x){
+ pa_assert(x_range_lower_i < n_points-1);
+ x_range_lower = (float) (xs[x_range_lower_i]);
+ x_range_upper = (float) (xs[x_range_lower_i+1]);
+ pa_assert_se(x_range_lower < x_range_upper);
+ pa_assert_se(x >= x_range_lower);
+ pa_assert_se(x <= x_range_upper);
+ //bilinear-interpolation of coefficients specified
+ c0 = (x-x_range_lower)/(x_range_upper-x_range_lower);
+ pa_assert_se(c0 >= 0&&c0 <= 1.0);
+ signal[x] = ((1.0f - c0) * ys[x_range_lower_i] + c0 * ys[x_range_lower_i + 1]);
+ while(x >= xs[x_range_lower_i + 1]){
+ x_range_lower_i++;
+ }
+ }
+ signal[length-1]=ys[n_points-1];
+}
+
+static int is_monotonic(const uint32_t *xs,size_t length){
+ if(length<2){
+ return 1;
+ }
+ for(size_t i = 1; i < length; ++i){
+ if(xs[i]<=xs[i-1]){
+ return 0;
+ }
+ }
+ return 1;
+}
+
+//ensure's memory allocated is a multiple of v_size
+//and aligned
+static void * alloc(size_t x,size_t s){
+ size_t f = PA_ROUND_UP(x*s, sizeof(float)*v_size);
+ float *t;
+ pa_assert(f >= x*s);
+ t = fftwf_malloc(f);
+ memset(t, 0, f);
+ return t;
+}
+
+static void alloc_input_buffers(struct userdata *u, size_t min_buffer_length){
+ if(min_buffer_length <= u->input_buffer_max){
+ return;
+ }
+ pa_assert(min_buffer_length >= u->window_size);
+ for(size_t c = 0; c < u->channels; ++c){
+ float *tmp = alloc(min_buffer_length, sizeof(float));
+ if(u->input[c]){
+ if(!u->first_iteration){
+ memcpy(tmp, u->input[c], u->overlap_size * sizeof(float));
+ }
+ free(u->input[c]);
+ }
+ u->input[c] = tmp;
+ }
+ u->input_buffer_max = min_buffer_length;
+}
+
+/* Called from I/O thread context */
+static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ //size_t fs=pa_frame_size(&u->sink->sample_spec);
+
+ /* The sink is _put() before the sink input is, so let's
+ * make sure we don't access it in that time. Also, the
+ * sink input is first shut down, the sink second. */
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
+ *((pa_usec_t*) data) = 0;
+ return 0;
+ }
+
+ *((pa_usec_t*) data) =
+ /* Get the latency of the master sink */
+ pa_sink_get_latency_within_thread(u->sink_input->sink) +
+
+ /* Add the latency internal to our sink input on top */
+ pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
+ // pa_bytes_to_usec(u->samples_gathered * fs, &u->sink->sample_spec);
+ //+ pa_bytes_to_usec(u->latency * fs, ss)
+ //+ pa_bytes_to_usec(pa_memblockq_get_length(u->input_q), ss);
+ return 0;
+ }
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+
+/* Called from main context */
+static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(state) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return 0;
+
+ pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
+ return 0;
+}
+
+/* Called from I/O thread context */
+static void sink_request_rewind_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+ return;
+
+ /* Just hand this one over to the master sink */
+ pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes+pa_memblockq_get_length(u->input_q), TRUE, FALSE, FALSE);
+}
+
+/* Called from I/O thread context */
+static void sink_update_requested_latency_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+ return;
+
+ /* Just hand this one over to the master sink */
+ pa_sink_input_set_requested_latency_within_thread(
+ u->sink_input,
+ pa_sink_get_requested_latency_within_thread(s));
+}
+
+/* Called from main context */
+static void sink_set_volume_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return;
+
+ pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE);
+}
+
+/* Called from main context */
+static void sink_set_mute_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return;
+
+ pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
+}
+
+#ifndef __SSE2__
+//reference implementation
+static void dsp_logic(
+ float * restrict dst,//used as a temp array too, needs to be fft_length!
+ float * restrict src,/*input data w/ overlap at start,
+ *automatically cycled in routine
+ */
+ float * restrict overlap,
+ const float X,//multipliar
+ const float * restrict H,//The freq. magnitude scalers filter
+ const float * restrict W,//The windowing function
+ fftwf_complex * restrict output_window,//The transformed window'd src
+ struct userdata *u){
+ //use a linear-phase sliding STFT and overlap-add method (for each channel)
+ //window the data
+ for(size_t j = 0; j < u->window_size; ++j){
+ dst[j] = X * W[j] * src[j];
+ }
+ //zero padd the the remaining fft window
+ memset(dst + u->window_size, 0, (u->fft_size - u->window_size) * sizeof(float));
+ //Processing is done here!
+ //do fft
+ fftwf_execute_dft_r2c(u->forward_plan, dst, output_window);
+ //perform filtering
+ for(size_t j = 0; j < FILTER_SIZE; ++j){
+ u->output_window[j][0] *= H[j];
+ u->output_window[j][1] *= H[j];
+ }
+ //inverse fft
+ fftwf_execute_dft_c2r(u->inverse_plan, output_window, dst);
+ ////debug: tests overlaping add
+ ////and negates ALL PREVIOUS processing
+ ////yields a perfect reconstruction if COLA is held
+ //for(size_t j = 0; j < u->window_size; ++j){
+ // u->work_buffer[j] = u->W[j] * u->input[c][j];
+ //}
+
+ //overlap add and preserve overlap component from this window (linear phase)
+ for(size_t j = 0; j < u->overlap_size; ++j){
+ u->work_buffer[j] += overlap[j];
+ overlap[j] = dst[u->R + j];
+ }
+ ////debug: tests if basic buffering works
+ ////shouldn't modify the signal AT ALL (beyond roundoff)
+ //for(size_t j = 0; j < u->window_size;++j){
+ // u->work_buffer[j] = u->input[c][j];
+ //}
+
+ //preseve the needed input for the next window's overlap
+ memmove(src, src + u->R,
+ (u->samples_gathered - u->R) * sizeof(float)
+ );
+}
+#else
+typedef float v4sf __attribute__ ((__aligned__(v_size * sizeof(float))));
+typedef union float_vector {
+ float f[v_size];
+ v4sf v;
+ __m128 m;
+} float_vector_t;
+
+//regardless of sse enabled, the loops in here assume
+//16 byte aligned addresses and memory allocations divisible by v_size
+static void dsp_logic(
+ float * restrict dst,//used as a temp array too, needs to be fft_length!
+ float * restrict src,/*input data w/ overlap at start,
+ *automatically cycled in routine
+ */
+ float * restrict overlap,//The size of the overlap
+ const float X,//multipliar
+ const float * restrict H,//The freq. magnitude scalers filter
+ const float * restrict W,//The windowing function
+ fftwf_complex * restrict output_window,//The transformed window'd src
+ struct userdata *u){//Collection of constants
+ const size_t overlap_size = PA_ROUND_UP(u->overlap_size, v_size);
+ float_vector_t x;
+ x.f[0] = x.f[1] = x.f[2] = x.f[3] = X;
+
+ //assert(u->samples_gathered >= u->R);
+ //use a linear-phase sliding STFT and overlap-add method
+ for(size_t j = 0; j < u->window_size; j += v_size){
+ //dst[j] = W[j] * src[j];
+ float_vector_t *d = (float_vector_t*) (dst + j);
+ float_vector_t *w = (float_vector_t*) (W + j);
+ float_vector_t *s = (float_vector_t*) (src + j);
+//#if __SSE2__
+ d->m = _mm_mul_ps(x.m, _mm_mul_ps(w->m, s->m));
+// d->v = x->v * w->v * s->v;
+//#endif
+ }
+ //zero padd the the remaining fft window
+ memset(dst + u->window_size, 0, (u->fft_size - u->window_size) * sizeof(float));
+
+ //Processing is done here!
+ //do fft
+ fftwf_execute_dft_r2c(u->forward_plan, dst, output_window);
+ //perform filtering - purely magnitude based
+ for(size_t j = 0; j < FILTER_SIZE; j += v_size / 2){
+ //output_window[j][0]*=H[j];
+ //output_window[j][1]*=H[j];
+ float_vector_t *d = (float_vector_t*)( ((float *) output_window) + 2 * j);
+ float_vector_t h;
+ h.f[0] = h.f[1] = H[j];
+ h.f[2] = h.f[3] = H[j + 1];
+//#if __SSE2__
+ d->m = _mm_mul_ps(d->m, h.m);
+//#else
+// d->v = d->v * h.v;
+//#endif
+ }
+
+ //inverse fft
+ fftwf_execute_dft_c2r(u->inverse_plan, output_window, dst);
+
+ ////debug: tests overlaping add
+ ////and negates ALL PREVIOUS processing
+ ////yields a perfect reconstruction if COLA is held
+ //for(size_t j = 0; j < u->window_size; ++j){
+ // dst[j] = W[j] * src[j];
+ //}
+
+ //overlap add and preserve overlap component from this window (linear phase)
+ for(size_t j = 0; j < overlap_size; j += v_size){
+ //dst[j]+=overlap[j];
+ //overlap[j]+=dst[j+R];
+ float_vector_t *d = (float_vector_t*)(dst + j);
+ float_vector_t *o = (float_vector_t*)(overlap + j);
+//#if __SSE2__
+ d->m = _mm_add_ps(d->m, o->m);
+ o->m = ((float_vector_t*)(dst + u->R + j))->m;
+//#else
+// d->v = d->v + o->v;
+// o->v = ((float_vector_t*)(dst + u->R + j))->v;
+//#endif
+ }
+ //memcpy(overlap, dst+u->R, u->overlap_size * sizeof(float)); //overlap preserve (debug)
+ //zero out the bit beyond the real overlap so we don't add garbage next iteration
+ memset(overlap + u->overlap_size, 0, overlap_size - u->overlap_size);
+
+ ////debug: tests if basic buffering works
+ ////shouldn't modify the signal AT ALL (beyond roundoff)
+ //for(size_t j = 0; j < u->window_size; ++j){
+ // dst[j] = src[j];
+ //}
+
+ //preseve the needed input for the next window's overlap
+ memmove(src, src + u->R,
+ (u->samples_gathered - u->R) * sizeof(float)
+ );
+}
+#endif
+
+static void process_samples(struct userdata *u, pa_memchunk *tchunk){
+ size_t fs = pa_frame_size(&(u->sink->sample_spec));
+ float *dst;
+ unsigned a_i;
+ float *H, X;
+ size_t iterations, offset;
+ pa_assert(u->samples_gathered >= u->window_size);
+ iterations = (u->samples_gathered - u->overlap_size) / u->R;
+ tchunk->index = 0;
+ tchunk->length = iterations * u->R * fs;
+ tchunk->memblock = pa_memblock_new(u->sink->core->mempool, tchunk->length);
+ dst = ((float*) pa_memblock_acquire(tchunk->memblock));
+ for(size_t iter = 0; iter < iterations; ++iter){
+ offset = iter * u->R * fs;
+ for(size_t c = 0;c < u->channels; c++) {
+ a_i = pa_aupdate_read_begin(u->a_H[c]);
+ X = u->Xs[c][a_i];
+ H = u->Hs[c][a_i];
+ dsp_logic(
+ u->work_buffer,
+ u->input[c],
+ u->overlap_accum[c],
+ X,
+ H,
+ u->W,
+ u->output_window,
+ u
+ );
+ pa_aupdate_read_end(u->a_H[c]);
+ if(u->first_iteration){
+ /* The windowing function will make the audio ramped in, as a cheap fix we can
+ * undo the windowing (for non-zero window values)
+ */
+ for(size_t i = 0; i < u->overlap_size; ++i){
+ u->work_buffer[i] = u->W[i] <= FLT_EPSILON ? u->work_buffer[i] : u->work_buffer[i] / u->W[i];
+ }
+ }
+ pa_sample_clamp(PA_SAMPLE_FLOAT32NE, (uint8_t *) (dst + c) + offset, fs, u->work_buffer, sizeof(float), u->R);
+ }
+ if(u->first_iteration){
+ u->first_iteration = FALSE;
+ }
+ u->samples_gathered -= u->R;
+ }
+ pa_memblock_release(tchunk->memblock);
+}
+
+static void input_buffer(struct userdata *u, pa_memchunk *in){
+ size_t fs = pa_frame_size(&(u->sink->sample_spec));
+ size_t samples = in->length/fs;
+ float *src = (float*) ((uint8_t*) pa_memblock_acquire(in->memblock) + in->index);
+ pa_assert(u->samples_gathered + samples <= u->input_buffer_max);
+ for(size_t c = 0; c < u->channels; c++) {
+ //buffer with an offset after the overlap from previous
+ //iterations
+ pa_assert_se(
+ u->input[c] + u->samples_gathered + samples <= u->input[c] + u->input_buffer_max
+ );
+ pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input[c] + u->samples_gathered, sizeof(float), src + c, fs, samples);
+ }
+ u->samples_gathered += samples;
+ pa_memblock_release(in->memblock);
+}
+
+/* Called from I/O thread context */
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
+ struct userdata *u;
+ size_t fs, target_samples, mbs;
+ //struct timeval start, end;
+ pa_memchunk tchunk;
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+ pa_assert(chunk);
+ pa_assert(u->sink);
+ fs = pa_frame_size(&(u->sink->sample_spec));
+ nbytes = PA_MIN(nbytes, pa_mempool_block_size_max(u->sink->core->mempool));
+ target_samples = PA_ROUND_UP(nbytes / fs, u->R);
+ mbs = pa_mempool_block_size_max(u->sink->core->mempool);
+ //pa_log_debug("vanilla mbs = %ld",mbs);
+ mbs = PA_ROUND_DOWN(mbs / fs, u->R);
+ mbs = PA_MAX(mbs, u->R);
+ target_samples = PA_MAX(target_samples, mbs);
+ //pa_log_debug("target samples: %ld", target_samples);
+ if(u->first_iteration){
+ //allocate request_size
+ target_samples = PA_MAX(target_samples, u->window_size);
+ }else{
+ //allocate request_size + overlap
+ target_samples += u->overlap_size;
+ }
+ alloc_input_buffers(u, target_samples);
+ //pa_log_debug("post target samples: %ld", target_samples);
+ chunk->memblock = NULL;
+
+ /* Hmm, process any rewind request that might be queued up */
+ pa_sink_process_rewind(u->sink, 0);
+
+ //pa_log_debug("start output-buffered %ld, input-buffered %ld, requested %ld",buffered_samples,u->samples_gathered,samples_requested);
+ //pa_rtclock_get(&start);
+ do{
+ size_t input_remaining = target_samples - u->samples_gathered;
+ // pa_log_debug("input remaining %ld samples", input_remaining);
+ pa_assert(input_remaining > 0);
+ while(pa_memblockq_peek(u->input_q, &tchunk) < 0){
+ //pa_sink_render(u->sink, input_remaining * fs, &tchunk);
+ pa_sink_render_full(u->sink, input_remaining * fs, &tchunk);
+ pa_assert(tchunk.memblock);
+ pa_memblockq_push(u->input_q, &tchunk);
+ pa_memblock_unref(tchunk.memblock);
+ }
+ pa_assert(tchunk.memblock);
+ tchunk.length = PA_MIN(input_remaining * fs, tchunk.length);
+ pa_memblockq_drop(u->input_q, tchunk.length);
+ //pa_log_debug("asked for %ld input samples, got %ld samples",input_remaining,buffer->length/fs);
+ /* copy new input */
+ //pa_rtclock_get(start);
+ // pa_log_debug("buffering %ld bytes", tchunk.length);
+ input_buffer(u, &tchunk);
+ //pa_rtclock_get(&end);
+ //pa_log_debug("Took %0.5f seconds to setup", pa_timeval_diff(end, start) / (double) PA_USEC_PER_SEC);
+ pa_memblock_unref(tchunk.memblock);
+ }while(u->samples_gathered < target_samples);
+
+ //pa_rtclock_get(&end);
+ //pa_log_debug("Took %0.6f seconds to get data", (double) pa_timeval_diff(&end, &start) / PA_USEC_PER_SEC);
+
+ pa_assert(u->fft_size >= u->window_size);
+ pa_assert(u->R < u->window_size);
+ //pa_rtclock_get(&start);
+ /* process a block */
+ process_samples(u, chunk);
+ //pa_rtclock_get(&end);
+ //pa_log_debug("Took %0.6f seconds to process", (double) pa_timeval_diff(&end, &start) / PA_USEC_PER_SEC);
+
+ pa_assert(chunk->memblock);
+ //pa_log_debug("gave %ld", chunk->length/fs);
+ //pa_log_debug("end pop");
+ return 0;
+}
+
+/* Called from main context */
+static void sink_input_volume_changed_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_volume_changed(u->sink, &i->volume);
+}
+
+/* Called from main context */
+static void sink_input_mute_changed_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_mute_changed(u->sink, i->muted);
+}
+
+static void reset_filter(struct userdata *u){
+ size_t fs = pa_frame_size(&u->sink->sample_spec);
+ size_t max_request;
+ u->samples_gathered = 0;
+ for(size_t i = 0; i < u->channels; ++i){
+ memset(u->overlap_accum[i], 0, u->overlap_size * sizeof(float));
+ }
+ u->first_iteration = TRUE;
+ //set buffer size to max request, no overlap copy
+ max_request = PA_ROUND_UP(pa_sink_input_get_max_request(u->sink_input) / fs , u->R);
+ max_request = PA_MAX(max_request, u->window_size);
+ pa_sink_set_max_request_within_thread(u->sink, max_request * fs);
+}
+
+/* Called from I/O thread context */
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+ size_t amount = 0;
+
+ pa_log_debug("Rewind callback!");
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ if (u->sink->thread_info.rewind_nbytes > 0) {
+ size_t max_rewrite;
+
+ //max_rewrite = nbytes;
+ max_rewrite = nbytes + pa_memblockq_get_length(u->input_q);
+ //PA_MIN(pa_memblockq_get_length(u->input_q), nbytes);
+ amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite);
+ u->sink->thread_info.rewind_nbytes = 0;
+
+ if (amount > 0) {
+ //invalidate the output q
+ pa_memblockq_seek(u->input_q, - (int64_t) amount, PA_SEEK_RELATIVE, TRUE);
+ pa_log("Resetting filter");
+ //reset_filter(u); //this is the "proper" thing to do...
+ }
+ }
+
+ pa_sink_process_rewind(u->sink, amount);
+ pa_memblockq_rewind(u->input_q, nbytes);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_memblockq_set_maxrewind(u->input_q, nbytes);
+ pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+ size_t fs;
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+ //if(u->first_iteration){
+ // return;
+ //}
+ fs = pa_frame_size(&(u->sink->sample_spec));
+ pa_sink_set_max_request_within_thread(u->sink, PA_ROUND_UP(nbytes / fs, u->R) * fs);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+}
+
+/* Called from I/O thread context */
+static void sink_input_detach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_detach_within_thread(u->sink);
+
+ pa_sink_set_rtpoll(u->sink, NULL);
+}
+
+/* Called from I/O thread context */
+static void sink_input_attach_cb(pa_sink_input *i) {
+ struct userdata *u;
+ size_t fs, max_request;
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
+ pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+
+ pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+ fs = pa_frame_size(&u->sink->sample_spec);
+ //set buffer size to max request, no overlap copy
+ max_request = PA_ROUND_UP(pa_sink_input_get_max_request(u->sink_input) / fs , u->R);
+ max_request = PA_MAX(max_request, u->window_size);
+ pa_sink_set_max_request_within_thread(u->sink, max_request * fs);
+ pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
+ pa_sink_attach_within_thread(u->sink);
+ if(u->set_default){
+ pa_log_debug("Setting default sink to %s", u->sink->name);
+ pa_namereg_set_default_sink(u->module->core, u->sink);
+ }
+}
+
+/* Called from main context */
+static void sink_input_kill_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ /* The order here matters! We first kill the sink input, followed
+ * by the sink. That means the sink callbacks must be protected
+ * against an unconnected sink input! */
+ pa_sink_input_unlink(u->sink_input);
+ pa_sink_unlink(u->sink);
+
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+
+ pa_module_unload_request(u->module, TRUE);
+}
+
+/* Called from IO thread context */
+static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ /* If we are added for the first time, ask for a rewinding so that
+ * we are heard right-away. */
+ if (PA_SINK_INPUT_IS_LINKED(state) &&
+ i->thread_info.state == PA_SINK_INPUT_INIT) {
+ pa_log_debug("Requesting rewind due to state change.");
+ pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE);
+ }
+}
+
+static void pack(char **strs, size_t len, char **packed, size_t *length){
+ size_t t_len = 0;
+ size_t headers = (1+len) * sizeof(uint16_t);
+ char *p;
+ for(size_t i = 0; i < len; ++i){
+ t_len += strlen(strs[i]);
+ }
+ *length = headers + t_len;
+ p = *packed = pa_xmalloc0(*length);
+ *((uint16_t *) p) = (uint16_t) len;
+ p += sizeof(uint16_t);
+ for(size_t i = 0; i < len; ++i){
+ uint16_t l = strlen(strs[i]);
+ *((uint16_t *) p) = (uint16_t) l;
+ p += sizeof(uint16_t);
+ memcpy(p, strs[i], l);
+ p += l;
+ }
+}
+static void unpack(char *str, size_t length, char ***strs, size_t *len){
+ char *p = str;
+ *len = *((uint16_t *) p);
+ p += sizeof(uint16_t);
+ *strs = pa_xnew(char *, *len);
+
+ for(size_t i = 0; i < *len; ++i){
+ size_t l = *((uint16_t *) p);
+ p += sizeof(uint16_t);
+ (*strs)[i] = pa_xnew(char, l + 1);
+ memcpy((*strs)[i], p, l);
+ (*strs)[i][l] = '\0';
+ p += l;
+ }
+}
+static void save_profile(struct userdata *u, size_t channel, char *name){
+ unsigned a_i;
+ const size_t profile_size = CHANNEL_PROFILE_SIZE * sizeof(float);
+ float *H_n, *profile;
+ const float *H;
+ pa_datum key, data;
+ profile = pa_xnew0(float, profile_size);
+ a_i = pa_aupdate_read_begin(u->a_H[channel]);
+ profile[0] = u->Xs[a_i][channel];
+ H = u->Hs[channel][a_i];
+ H_n = profile + 1;
+ for(size_t i = 0 ; i <= FILTER_SIZE; ++i){
+ H_n[i] = H[i] * u->fft_size;
+ //H_n[i] = H[i];
+ }
+ pa_aupdate_read_end(u->a_H[channel]);
+ key.data=name;
+ key.size = strlen(key.data);
+ data.data = profile;
+ data.size = profile_size;
+ pa_database_set(u->database, &key, &data, TRUE);
+ pa_database_sync(u->database);
+ if(u->base_profiles[channel]){
+ pa_xfree(u->base_profiles[channel]);
+ }
+ u->base_profiles[channel] = pa_xstrdup(name);
+}
+
+static void save_state(struct userdata *u){
+ unsigned a_i;
+ const size_t filter_state_size = FILTER_STATE_SIZE * sizeof(float);
+ float *H_n, *state;
+ float *H;
+ pa_datum key, data;
+ pa_database *database;
+ char *dbname;
+ char *state_name = u->name;
+ char *packed;
+ size_t packed_length;
+
+ pack(u->base_profiles, u->channels, &packed, &packed_length);
+ state = (float *) pa_xmalloc0(filter_state_size + packed_length);
+ memcpy(state + FILTER_STATE_SIZE, packed, packed_length);
+ pa_xfree(packed);
+
+ for(size_t c = 0; c < u->channels; ++c){
+ a_i = pa_aupdate_read_begin(u->a_H[c]);
+ state[c * CHANNEL_PROFILE_SIZE] = u->Xs[c][a_i];
+ H = u->Hs[c][a_i];
+ H_n = &state[c * CHANNEL_PROFILE_SIZE + 1];
+ memcpy(H_n, H, FILTER_SIZE * sizeof(float));
+ pa_aupdate_read_end(u->a_H[c]);
+ }
+
+ key.data = state_name;
+ key.size = strlen(key.data);
+ data.data = state;
+ data.size = filter_state_size + packed_length;
+ //thread safety for 0.9.17?
+ pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, FALSE));
+ pa_assert_se(database = pa_database_open(dbname, TRUE));
+ pa_xfree(dbname);
+
+ pa_database_set(database, &key, &data, TRUE);
+ pa_database_sync(database);
+ pa_database_close(database);
+ pa_xfree(state);
+}
+
+static void remove_profile(pa_core *c, char *name){
+ pa_datum key;
+ pa_database *database;
+ key.data = name;
+ key.size = strlen(key.data);
+ pa_assert_se(database = pa_shared_get(c, EQDB));
+ pa_database_unset(database, &key);
+ pa_database_sync(database);
+}
+
+static const char* load_profile(struct userdata *u, size_t channel, char *name){
+ unsigned a_i;
+ pa_datum key, value;
+ const size_t profile_size = CHANNEL_PROFILE_SIZE * sizeof(float);
+ key.data = name;
+ key.size = strlen(key.data);
+ if(pa_database_get(u->database, &key, &value) != NULL){
+ if(value.size == profile_size){
+ float *profile = (float *) value.data;
+ a_i = pa_aupdate_write_begin(u->a_H[channel]);
+ u->Xs[channel][a_i] = profile[0];
+ memcpy(u->Hs[channel][a_i], profile + 1, FILTER_SIZE * sizeof(float));
+ fix_filter(u->Hs[channel][a_i], u->fft_size);
+ pa_aupdate_write_end(u->a_H[channel]);
+ pa_xfree(u->base_profiles[channel]);
+ u->base_profiles[channel] = pa_xstrdup(name);
+ }else{
+ return "incompatible size";
+ }
+ pa_datum_free(&value);
+ }else{
+ return "profile doesn't exist";
+ }
+ return NULL;
+}
+
+static void load_state(struct userdata *u){
+ unsigned a_i;
+ float *H;
+ pa_datum key, value;
+ pa_database *database;
+ char *dbname;
+ char *state_name = u->name;
+ pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, FALSE));
+ database = pa_database_open(dbname, FALSE);
+ pa_xfree(dbname);
+ if(!database){
+ pa_log("No resume state");
+ return;
+ }
+
+ key.data = state_name;
+ key.size = strlen(key.data);
+
+ if(pa_database_get(database, &key, &value) != NULL){
+ if(value.size > FILTER_STATE_SIZE * sizeof(float) + sizeof(uint16_t)){
+ float *state = (float *) value.data;
+ size_t n_profs;
+ char **names;
+ for(size_t c = 0; c < u->channels; ++c){
+ a_i = pa_aupdate_write_begin(u->a_H[c]);
+ H = state + c * CHANNEL_PROFILE_SIZE + 1;
+ u->Xs[c][a_i] = state[c * CHANNEL_PROFILE_SIZE];
+ memcpy(u->Hs[c][a_i], H, FILTER_SIZE * sizeof(float));
+ pa_aupdate_write_end(u->a_H[c]);
+ }
+ unpack(((char *)value.data) + FILTER_STATE_SIZE * sizeof(float), value.size - FILTER_STATE_SIZE * sizeof(float), &names, &n_profs);
+ n_profs = PA_MIN(n_profs, u->channels);
+ for(size_t c = 0; c < n_profs; ++c){
+ pa_xfree(u->base_profiles[c]);
+ u->base_profiles[c] = names[c];
+ }
+ pa_xfree(names);
+ }
+ pa_datum_free(&value);
+ }else{
+ pa_log("resume state exists but is wrong size!");
+ }
+ pa_database_close(database);
+}
+
+/* Called from main context */
+static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ return u->sink != dest;
+}
+
+/* Called from main context */
+static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+ if (dest) {
+ pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
+ pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
+ } else
+ pa_sink_set_asyncmsgq(u->sink, NULL);
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma;
+ const char *z;
+ pa_sink *master;
+ pa_sink_input_new_data sink_input_data;
+ pa_sink_new_data sink_data;
+ size_t fs;
+ float *H;
+ unsigned a_i;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK))) {
+ pa_log("Master sink not found, trying default");
+ master = pa_namereg_get_default_sink(m->core);
+ if(!master){
+ pa_log("no default sink found!");
+ goto fail;
+ }
+ }
+
+ ss = master->sample_spec;
+ ss.format = PA_SAMPLE_FLOAT32;
+ map = master->channel_map;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("Invalid sample format specification or channel map");
+ goto fail;
+ }
+ fs = pa_frame_size(&ss);
+
+ u = pa_xnew0(struct userdata, 1);
+ u->module = m;
+ m->userdata = u;
+
+ u->set_default = TRUE;
+ pa_modargs_get_value_boolean(ma, "set_default", &u->set_default);
+
+ u->channels = ss.channels;
+ u->fft_size = pow(2, ceil(log(ss.rate) / log(2)));//probably unstable near corner cases of powers of 2
+ pa_log_debug("fft size: %ld", u->fft_size);
+ u->window_size = 15999;
+ if(u->window_size % 2 == 0){
+ u->window_size--;
+ }
+ u->R = (u->window_size + 1) / 2;
+ u->overlap_size = u->window_size - u->R;
+ u->samples_gathered = 0;
+ u->input_buffer_max = 0;
+ u->a_H = pa_xnew0(pa_aupdate *, u->channels);
+ u->Xs = pa_xnew0(float *, u->channels);
+ u->Hs = pa_xnew0(float **, u->channels);
+ for(size_t c = 0; c < u->channels; ++c){
+ u->Xs[c] = pa_xnew0(float, 2);
+ u->Hs[c] = pa_xnew0(float *, 2);
+ for(size_t i = 0; i < 2; ++i){
+ u->Hs[c][i] = alloc(FILTER_SIZE, sizeof(float));
+ }
+ }
+ u->W = alloc(u->window_size, sizeof(float));
+ u->work_buffer = alloc(u->fft_size, sizeof(float));
+ memset(u->work_buffer, 0, u->fft_size*sizeof(float));
+ u->input = pa_xnew0(float *, u->channels);
+ u->overlap_accum = pa_xnew0(float *, u->channels);
+ for(size_t c = 0; c < u->channels; ++c){
+ u->a_H[c] = pa_aupdate_new();
+ u->input[c] = NULL;
+ u->overlap_accum[c] = alloc(u->overlap_size, sizeof(float));
+ }
+ u->output_window = alloc((FILTER_SIZE), sizeof(fftwf_complex));
+ u->forward_plan = fftwf_plan_dft_r2c_1d(u->fft_size, u->work_buffer, u->output_window, FFTW_ESTIMATE);
+ u->inverse_plan = fftwf_plan_dft_c2r_1d(u->fft_size, u->output_window, u->work_buffer, FFTW_ESTIMATE);
+
+ hanning_window(u->W, u->window_size);
+ u->first_iteration = TRUE;
+
+ u->base_profiles = pa_xnew0(char *, u->channels);
+ for(size_t c = 0; c < u->channels; ++c){
+ u->base_profiles[c] = pa_xstrdup("default");
+ }
+
+ /* Create sink */
+ pa_sink_new_data_init(&sink_data);
+ sink_data.driver = __FILE__;
+ sink_data.module = m;
+ if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
+ sink_data.name = pa_sprintf_malloc("%s.equalizer", master->name);
+ pa_sink_new_data_set_sample_spec(&sink_data, &ss);
+ pa_sink_new_data_set_channel_map(&sink_data, &map);
+ z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "FFT based equalizer on %s",z? z: master->name);
+ pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
+ pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
+
+ if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
+ pa_log("Invalid properties");
+ pa_sink_new_data_done(&sink_data);
+ goto fail;
+ }
+
+ u->sink = pa_sink_new(m->core, &sink_data,
+ PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME|
+ (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
+ pa_sink_new_data_done(&sink_data);
+
+ if (!u->sink) {
+ pa_log("Failed to create sink.");
+ goto fail;
+ }
+ u->name=pa_xstrdup(u->sink->name);
+ u->sink->parent.process_msg = sink_process_msg_cb;
+ u->sink->set_state = sink_set_state_cb;
+ u->sink->update_requested_latency = sink_update_requested_latency_cb;
+ u->sink->request_rewind = sink_request_rewind_cb;
+ u->sink->set_volume = sink_set_volume_cb;
+ u->sink->set_mute = sink_set_mute_cb;
+ u->sink->userdata = u;
+ u->input_q = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, fs, 1, 1, 0, &u->sink->silence);
+
+ pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
+ //pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->R*fs, &ss));
+
+ /* Create sink input */
+ pa_sink_input_new_data_init(&sink_input_data);
+ sink_input_data.driver = __FILE__;
+ sink_input_data.module = m;
+ sink_input_data.sink = master;
+ pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Equalized Stream");
+ pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
+ pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
+ pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
+
+ pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
+ pa_sink_input_new_data_done(&sink_input_data);
+
+ if (!u->sink_input)
+ goto fail;
+
+ u->sink_input->pop = sink_input_pop_cb;
+ u->sink_input->process_rewind = sink_input_process_rewind_cb;
+ u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
+ u->sink_input->update_max_request = sink_input_update_max_request_cb;
+ u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
+ u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->attach = sink_input_attach_cb;
+ u->sink_input->detach = sink_input_detach_cb;
+ u->sink_input->state_change = sink_input_state_change_cb;
+ u->sink_input->may_move_to = sink_input_may_move_to_cb;
+ u->sink_input->moving = sink_input_moving_cb;
+ u->sink_input->volume_changed = sink_input_volume_changed_cb;
+ u->sink_input->mute_changed = sink_input_mute_changed_cb;
+
+ u->sink_input->userdata = u;
+
+ pa_sink_put(u->sink);
+ pa_sink_input_put(u->sink_input);
+
+ pa_modargs_free(ma);
+
+
+ dbus_init(u);
+
+ //default filter to these
+ for(size_t c = 0; c< u->channels; ++c){
+ a_i = pa_aupdate_write_begin(u->a_H[c]);
+ H = u->Hs[c][a_i];
+ u->Xs[c][a_i] = 1.0f;
+ for(size_t i = 0; i < FILTER_SIZE; ++i){
+ H[i] = 1.0 / sqrtf(2.0f);
+ }
+ fix_filter(H, u->fft_size);
+ pa_aupdate_write_end(u->a_H[c]);
+ }
+ //load old parameters
+ load_state(u);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+
+ pa__done(m);
+
+ return -1;
+}
+
+int pa__get_n_used(pa_module *m) {
+ struct userdata *u;
+
+ pa_assert(m);
+ pa_assert_se(u = m->userdata);
+
+ return pa_sink_linked_by(u->sink);
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ save_state(u);
+
+ dbus_done(u);
+
+ for(size_t c = 0; c < u->channels; ++c){
+ pa_xfree(u->base_profiles[c]);
+ }
+ pa_xfree(u->base_profiles);
+
+ /* See comments in sink_input_kill_cb() above regarding
+ * destruction order! */
+
+ if (u->sink_input)
+ pa_sink_input_unlink(u->sink_input);
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->sink_input)
+ pa_sink_input_unref(u->sink_input);
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ pa_memblockq_free(u->input_q);
+
+ fftwf_destroy_plan(u->inverse_plan);
+ fftwf_destroy_plan(u->forward_plan);
+ pa_xfree(u->output_window);
+ for(size_t c=0; c < u->channels; ++c){
+ pa_aupdate_free(u->a_H[c]);
+ pa_xfree(u->overlap_accum[c]);
+ pa_xfree(u->input[c]);
+ }
+ pa_xfree(u->a_H);
+ pa_xfree(u->overlap_accum);
+ pa_xfree(u->input);
+ pa_xfree(u->work_buffer);
+ pa_xfree(u->W);
+ for(size_t c = 0; c < u->channels; ++c){
+ pa_xfree(u->Xs[c]);
+ for(size_t i = 0; i < 2; ++i){
+ pa_xfree(u->Hs[c][i]);
+ }
+ pa_xfree(u->Hs[c]);
+ }
+ pa_xfree(u->Xs);
+ pa_xfree(u->Hs);
+
+ pa_xfree(u->name);
+
+ pa_xfree(u);
+}
+
+/*
+ * DBus Routines and Callbacks
+ */
+#define EXTNAME "org.PulseAudio.Ext.Equalizing1"
+#define MANAGER_PATH "/org/pulseaudio/equalizing1"
+#define MANAGER_IFACE EXTNAME ".Manager"
+#define EQUALIZER_IFACE EXTNAME ".Equalizer"
+static void manager_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void manager_get_sinks(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void manager_get_profiles(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void manager_get_all(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void manager_handle_remove_profile(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_get_filter_rate(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_get_n_coefs(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_get_n_channels(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_get_all(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_handle_seed_filter(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_handle_get_filter_points(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_handle_get_filter(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_handle_set_filter(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_handle_save_profile(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_handle_load_profile(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_handle_save_state(DBusConnection *conn, DBusMessage *msg, void *_u);
+static void equalizer_handle_get_profile_name(DBusConnection *conn, DBusMessage *msg, void *_u);
+enum manager_method_index {
+ MANAGER_METHOD_REMOVE_PROFILE,
+ MANAGER_METHOD_MAX
+};
+
+pa_dbus_arg_info remove_profile_args[]={
+ {"name", "s","in"},
+};
+
+static pa_dbus_method_handler manager_methods[MANAGER_METHOD_MAX]={
+ [MANAGER_METHOD_REMOVE_PROFILE]{
+ .method_name="RemoveProfile",
+ .arguments=remove_profile_args,
+ .n_arguments=sizeof(remove_profile_args)/sizeof(pa_dbus_arg_info),
+ .receive_cb=manager_handle_remove_profile}
+};
+
+enum manager_handler_index {
+ MANAGER_HANDLER_REVISION,
+ MANAGER_HANDLER_EQUALIZED_SINKS,
+ MANAGER_HANDLER_PROFILES,
+ MANAGER_HANDLER_MAX
+};
+
+static pa_dbus_property_handler manager_handlers[MANAGER_HANDLER_MAX]={
+ [MANAGER_HANDLER_REVISION]={.property_name="InterfaceRevision",.type="u",.get_cb=manager_get_revision,.set_cb=NULL},
+ [MANAGER_HANDLER_EQUALIZED_SINKS]={.property_name="EqualizedSinks",.type="ao",.get_cb=manager_get_sinks,.set_cb=NULL},
+ [MANAGER_HANDLER_PROFILES]={.property_name="Profiles",.type="as",.get_cb=manager_get_profiles,.set_cb=NULL}
+};
+
+pa_dbus_arg_info sink_args[]={
+ {"sink", "o", NULL}
+};
+
+enum manager_signal_index{
+ MANAGER_SIGNAL_SINK_ADDED,
+ MANAGER_SIGNAL_SINK_REMOVED,
+ MANAGER_SIGNAL_PROFILES_CHANGED,
+ MANAGER_SIGNAL_MAX
+};
+
+static pa_dbus_signal_info manager_signals[MANAGER_SIGNAL_MAX]={
+ [MANAGER_SIGNAL_SINK_ADDED]={.name="SinkAdded", .arguments=sink_args, .n_arguments=sizeof(sink_args)/sizeof(pa_dbus_arg_info)},
+ [MANAGER_SIGNAL_SINK_REMOVED]={.name="SinkRemoved", .arguments=sink_args, .n_arguments=sizeof(sink_args)/sizeof(pa_dbus_arg_info)},
+ [MANAGER_SIGNAL_PROFILES_CHANGED]={.name="ProfilesChanged", .arguments=NULL, .n_arguments=0}
+};
+
+static pa_dbus_interface_info manager_info={
+ .name=MANAGER_IFACE,
+ .method_handlers=manager_methods,
+ .n_method_handlers=MANAGER_METHOD_MAX,
+ .property_handlers=manager_handlers,
+ .n_property_handlers=MANAGER_HANDLER_MAX,
+ .get_all_properties_cb=manager_get_all,
+ .signals=manager_signals,
+ .n_signals=MANAGER_SIGNAL_MAX
+};
+
+enum equalizer_method_index {
+ EQUALIZER_METHOD_FILTER_POINTS,
+ EQUALIZER_METHOD_SEED_FILTER,
+ EQUALIZER_METHOD_SAVE_PROFILE,
+ EQUALIZER_METHOD_LOAD_PROFILE,
+ EQUALIZER_METHOD_SET_FILTER,
+ EQUALIZER_METHOD_GET_FILTER,
+ EQUALIZER_METHOD_SAVE_STATE,
+ EQUALIZER_METHOD_GET_PROFILE_NAME,
+ EQUALIZER_METHOD_MAX
+};
+
+enum equalizer_handler_index {
+ EQUALIZER_HANDLER_REVISION,
+ EQUALIZER_HANDLER_SAMPLERATE,
+ EQUALIZER_HANDLER_FILTERSAMPLERATE,
+ EQUALIZER_HANDLER_N_COEFS,
+ EQUALIZER_HANDLER_N_CHANNELS,
+ EQUALIZER_HANDLER_MAX
+};
+
+pa_dbus_arg_info filter_points_args[]={
+ {"channel", "u","in"},
+ {"xs", "au","in"},
+ {"ys", "ad","out"},
+ {"preamp", "d","out"}
+};
+pa_dbus_arg_info seed_filter_args[]={
+ {"channel", "u","in"},
+ {"xs", "au","in"},
+ {"ys", "ad","in"},
+ {"preamp", "d","in"}
+};
+
+pa_dbus_arg_info set_filter_args[]={
+ {"channel", "u","in"},
+ {"ys", "ad","in"},
+ {"preamp", "d","in"}
+};
+pa_dbus_arg_info get_filter_args[]={
+ {"channel", "u","in"},
+ {"ys", "ad","out"},
+ {"preamp", "d","out"}
+};
+
+pa_dbus_arg_info save_profile_args[]={
+ {"channel", "u","in"},
+ {"name", "s","in"}
+};
+pa_dbus_arg_info load_profile_args[]={
+ {"channel", "u","in"},
+ {"name", "s","in"}
+};
+pa_dbus_arg_info base_profile_name_args[]={
+ {"channel", "u","in"},
+ {"name", "s","out"}
+};
+
+static pa_dbus_method_handler equalizer_methods[EQUALIZER_METHOD_MAX]={
+ [EQUALIZER_METHOD_SEED_FILTER]{
+ .method_name="SeedFilter",
+ .arguments=seed_filter_args,
+ .n_arguments=sizeof(seed_filter_args)/sizeof(pa_dbus_arg_info),
+ .receive_cb=equalizer_handle_seed_filter},
+ [EQUALIZER_METHOD_FILTER_POINTS]{
+ .method_name="FilterAtPoints",
+ .arguments=filter_points_args,
+ .n_arguments=sizeof(filter_points_args)/sizeof(pa_dbus_arg_info),
+ .receive_cb=equalizer_handle_get_filter_points},
+ [EQUALIZER_METHOD_SET_FILTER]{
+ .method_name="SetFilter",
+ .arguments=set_filter_args,
+ .n_arguments=sizeof(set_filter_args)/sizeof(pa_dbus_arg_info),
+ .receive_cb=equalizer_handle_set_filter},
+ [EQUALIZER_METHOD_GET_FILTER]{
+ .method_name="GetFilter",
+ .arguments=get_filter_args,
+ .n_arguments=sizeof(get_filter_args)/sizeof(pa_dbus_arg_info),
+ .receive_cb=equalizer_handle_get_filter},
+ [EQUALIZER_METHOD_SAVE_PROFILE]{
+ .method_name="SaveProfile",
+ .arguments=save_profile_args,
+ .n_arguments=sizeof(save_profile_args)/sizeof(pa_dbus_arg_info),
+ .receive_cb=equalizer_handle_save_profile},
+ [EQUALIZER_METHOD_LOAD_PROFILE]{
+ .method_name="LoadProfile",
+ .arguments=load_profile_args,
+ .n_arguments=sizeof(load_profile_args)/sizeof(pa_dbus_arg_info),
+ .receive_cb=equalizer_handle_load_profile},
+ [EQUALIZER_METHOD_SAVE_STATE]{
+ .method_name="SaveState",
+ .arguments=NULL,
+ .n_arguments=0,
+ .receive_cb=equalizer_handle_save_state},
+ [EQUALIZER_METHOD_GET_PROFILE_NAME]{
+ .method_name="BaseProfile",
+ .arguments=base_profile_name_args,
+ .n_arguments=sizeof(base_profile_name_args)/sizeof(pa_dbus_arg_info),
+ .receive_cb=equalizer_handle_get_profile_name}
+};
+
+static pa_dbus_property_handler equalizer_handlers[EQUALIZER_HANDLER_MAX]={
+ [EQUALIZER_HANDLER_REVISION]={.property_name="InterfaceRevision",.type="u",.get_cb=equalizer_get_revision,.set_cb=NULL},
+ [EQUALIZER_HANDLER_SAMPLERATE]{.property_name="SampleRate",.type="u",.get_cb=equalizer_get_sample_rate,.set_cb=NULL},
+ [EQUALIZER_HANDLER_FILTERSAMPLERATE]{.property_name="FilterSampleRate",.type="u",.get_cb=equalizer_get_filter_rate,.set_cb=NULL},
+ [EQUALIZER_HANDLER_N_COEFS]{.property_name="NFilterCoefficients",.type="u",.get_cb=equalizer_get_n_coefs,.set_cb=NULL},
+ [EQUALIZER_HANDLER_N_CHANNELS]{.property_name="NChannels",.type="u",.get_cb=equalizer_get_n_channels,.set_cb=NULL},
+};
+
+enum equalizer_signal_index{
+ EQUALIZER_SIGNAL_FILTER_CHANGED,
+ EQUALIZER_SIGNAL_SINK_RECONFIGURED,
+ EQUALIZER_SIGNAL_MAX
+};
+
+static pa_dbus_signal_info equalizer_signals[EQUALIZER_SIGNAL_MAX]={
+ [EQUALIZER_SIGNAL_FILTER_CHANGED]={.name="FilterChanged", .arguments=NULL, .n_arguments=0},
+ [EQUALIZER_SIGNAL_SINK_RECONFIGURED]={.name="SinkReconfigured", .arguments=NULL, .n_arguments=0},
+};
+
+static pa_dbus_interface_info equalizer_info={
+ .name=EQUALIZER_IFACE,
+ .method_handlers=equalizer_methods,
+ .n_method_handlers=EQUALIZER_METHOD_MAX,
+ .property_handlers=equalizer_handlers,
+ .n_property_handlers=EQUALIZER_HANDLER_MAX,
+ .get_all_properties_cb=equalizer_get_all,
+ .signals=equalizer_signals,
+ .n_signals=EQUALIZER_SIGNAL_MAX
+};
+
+void dbus_init(struct userdata *u){
+ uint32_t dummy;
+ DBusMessage *signal = NULL;
+ pa_idxset *sink_list = NULL;
+ u->dbus_protocol=pa_dbus_protocol_get(u->sink->core);
+ u->dbus_path=pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->sink->index);
+
+ pa_dbus_protocol_add_interface(u->dbus_protocol, u->dbus_path, &equalizer_info, u);
+ sink_list = pa_shared_get(u->sink->core, SINKLIST);
+ u->database = pa_shared_get(u->sink->core, EQDB);
+ if(sink_list == NULL){
+ char *dbname;
+ sink_list=pa_idxset_new(&pa_idxset_trivial_hash_func, &pa_idxset_trivial_compare_func);
+ pa_shared_set(u->sink->core, SINKLIST, sink_list);
+ pa_assert_se(dbname = pa_state_path("equalizer-presets", FALSE));
+ pa_assert_se(u->database = pa_database_open(dbname, TRUE));
+ pa_xfree(dbname);
+ pa_shared_set(u->sink->core, EQDB, u->database);
+ pa_dbus_protocol_add_interface(u->dbus_protocol, MANAGER_PATH, &manager_info, u->sink->core);
+ pa_dbus_protocol_register_extension(u->dbus_protocol, EXTNAME);
+ }
+ pa_idxset_put(sink_list, u, &dummy);
+
+ pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_SINK_ADDED].name)));
+ dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &u->dbus_path, DBUS_TYPE_INVALID);
+ pa_dbus_protocol_send_signal(u->dbus_protocol, signal);
+ dbus_message_unref(signal);
+}
+
+void dbus_done(struct userdata *u){
+ pa_idxset *sink_list;
+ uint32_t dummy;
+
+ DBusMessage *signal = NULL;
+ pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_SINK_REMOVED].name)));
+ dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &u->dbus_path, DBUS_TYPE_INVALID);
+ pa_dbus_protocol_send_signal(u->dbus_protocol, signal);
+ dbus_message_unref(signal);
+
+ pa_assert_se(sink_list=pa_shared_get(u->sink->core,SINKLIST));
+ pa_idxset_remove_by_data(sink_list,u,&dummy);
+ if(pa_idxset_size(sink_list)==0){
+ pa_dbus_protocol_unregister_extension(u->dbus_protocol, EXTNAME);
+ pa_dbus_protocol_remove_interface(u->dbus_protocol, MANAGER_PATH, manager_info.name);
+ pa_shared_remove(u->sink->core, EQDB);
+ pa_database_close(u->database);
+ pa_shared_remove(u->sink->core, SINKLIST);
+ pa_xfree(sink_list);
+ }
+ pa_dbus_protocol_remove_interface(u->dbus_protocol, u->dbus_path, equalizer_info.name);
+ pa_xfree(u->dbus_path);
+ pa_dbus_protocol_unref(u->dbus_protocol);
+}
+
+void manager_handle_remove_profile(DBusConnection *conn, DBusMessage *msg, void *_u) {
+ DBusError error;
+ pa_core *c = (pa_core *)_u;
+ DBusMessage *signal = NULL;
+ pa_dbus_protocol *dbus_protocol;
+ char *name;
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(c);
+ dbus_error_init(&error);
+ if(!dbus_message_get_args(msg, &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message);
+ dbus_error_free(&error);
+ return;
+ }
+ remove_profile(c,name);
+ pa_dbus_send_empty_reply(conn, msg);
+
+ pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_PROFILES_CHANGED].name)));
+ dbus_protocol = pa_dbus_protocol_get(c);
+ pa_dbus_protocol_send_signal(dbus_protocol, signal);
+ pa_dbus_protocol_unref(dbus_protocol);
+ dbus_message_unref(signal);
+}
+
+void manager_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u){
+ uint32_t rev=1;
+ pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_UINT32, &rev);
+}
+
+static void get_sinks(pa_core *u, char ***names, unsigned *n_sinks){
+ void *iter = NULL;
+ struct userdata *sink_u = NULL;
+ uint32_t dummy;
+ pa_idxset *sink_list;
+ pa_assert(u);
+ pa_assert(names);
+ pa_assert(n_sinks);
+
+ pa_assert_se(sink_list = pa_shared_get(u, SINKLIST));
+ *n_sinks = (unsigned) pa_idxset_size(sink_list);
+ *names = *n_sinks > 0 ? pa_xnew0(char *,*n_sinks) : NULL;
+ for(uint32_t i = 0; i < *n_sinks; ++i){
+ sink_u = (struct userdata *) pa_idxset_iterate(sink_list, &iter, &dummy);
+ (*names)[i] = pa_xstrdup(sink_u->dbus_path);
+ }
+}
+
+void manager_get_sinks(DBusConnection *conn, DBusMessage *msg, void *_u){
+ unsigned n;
+ char **names = NULL;
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(_u);
+
+ get_sinks((pa_core *) _u, &names, &n);
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, names, n);
+ for(unsigned i = 0; i < n; ++i){
+ pa_xfree(names[i]);
+ }
+ pa_xfree(names);
+}
+
+static void get_profiles(pa_core *c, char ***names, unsigned *n){
+ char *name;
+ pa_database *database;
+ pa_datum key, next_key;
+ pa_strlist *head=NULL, *iter;
+ pa_bool_t done;
+ pa_assert_se(database = pa_shared_get(c, EQDB));
+
+ pa_assert(c);
+ pa_assert(names);
+ pa_assert(n);
+ done = !pa_database_first(database, &key, NULL);
+ *n = 0;
+ while(!done){
+ done = !pa_database_next(database, &key, &next_key, NULL);
+ name=pa_xmalloc(key.size + 1);
+ memcpy(name, key.data, key.size);
+ name[key.size] = '\0';
+ pa_datum_free(&key);
+ head = pa_strlist_prepend(head, name);
+ pa_xfree(name);
+ key = next_key;
+ (*n)++;
+ }
+ (*names) = *n > 0 ? pa_xnew0(char *, *n) : NULL;
+ iter=head;
+ for(unsigned i = 0; i < *n; ++i){
+ (*names)[*n - 1 - i] = pa_xstrdup(pa_strlist_data(iter));
+ iter = pa_strlist_next(iter);
+ }
+ pa_strlist_free(head);
+}
+
+void manager_get_profiles(DBusConnection *conn, DBusMessage *msg, void *_u){
+ char **names;
+ unsigned n;
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(_u);
+
+ get_profiles((pa_core *)_u, &names, &n);
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_STRING, names, n);
+ for(unsigned i = 0; i < n; ++i){
+ pa_xfree(names[i]);
+ }
+ pa_xfree(names);
+}
+
+void manager_get_all(DBusConnection *conn, DBusMessage *msg, void *_u){
+ pa_core *c;
+ char **names = NULL;
+ unsigned n;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter, dict_iter;
+ uint32_t rev;
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert_se(c = _u);
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ rev = 1;
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, manager_handlers[MANAGER_HANDLER_REVISION].property_name, DBUS_TYPE_UINT32, &rev);
+
+ get_sinks(c, &names, &n);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter,manager_handlers[MANAGER_HANDLER_EQUALIZED_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, names, n);
+ for(unsigned i = 0; i < n; ++i){
+ pa_xfree(names[i]);
+ }
+ pa_xfree(names);
+
+ get_profiles(c, &names, &n);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, manager_handlers[MANAGER_HANDLER_PROFILES].property_name, DBUS_TYPE_STRING, names, n);
+ for(unsigned i = 0; i < n; ++i){
+ pa_xfree(names[i]);
+ }
+ pa_xfree(names);
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+ dbus_message_unref(reply);
+}
+
+void equalizer_handle_seed_filter(DBusConnection *conn, DBusMessage *msg, void *_u) {
+ struct userdata *u=(struct userdata *) _u;
+ DBusError error;
+ DBusMessage *signal = NULL;
+ float *ys;
+ uint32_t *xs, channel, r_channel;
+ double *_ys, preamp;
+ unsigned x_npoints, y_npoints, a_i;
+ float *H;
+ pa_bool_t points_good = TRUE;
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(u);
+
+ dbus_error_init(&error);
+
+ if(!dbus_message_get_args(msg, &error,
+ DBUS_TYPE_UINT32, &channel,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &xs, &x_npoints,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_DOUBLE, &_ys, &y_npoints,
+ DBUS_TYPE_DOUBLE, &preamp,
+ DBUS_TYPE_INVALID)){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message);
+ dbus_error_free(&error);
+ return;
+ }
+ if(channel > u->channels){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel);
+ dbus_error_free(&error);
+ return;
+ }
+ for(size_t i = 0; i < x_npoints; ++i){
+ if(xs[i] >= FILTER_SIZE){
+ points_good = FALSE;
+ break;
+ }
+ }
+ if(!is_monotonic(xs, x_npoints) || !points_good){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs must be monotonic and 0<=x<=%ld", u->fft_size / 2);
+ dbus_error_free(&error);
+ return;
+ }else if(x_npoints != y_npoints || x_npoints < 2 || x_npoints > FILTER_SIZE ){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs and ys must be the same length and 2<=l<=%ld!", FILTER_SIZE);
+ dbus_error_free(&error);
+ return;
+ }else if(xs[0] != 0 || xs[x_npoints - 1] != u->fft_size / 2){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs[0] must be 0 and xs[-1]=fft_size/2");
+ dbus_error_free(&error);
+ return;
+ }
+
+ ys = pa_xmalloc(x_npoints * sizeof(float));
+ for(uint32_t i = 0; i < x_npoints; ++i){
+ ys[i] = (float) _ys[i];
+ }
+ r_channel = channel == u->channels ? 0 : channel;
+ a_i = pa_aupdate_write_begin(u->a_H[r_channel]);
+ H = u->Hs[r_channel][a_i];
+ u->Xs[r_channel][a_i] = preamp;
+ interpolate(H, FILTER_SIZE, xs, ys, x_npoints);
+ fix_filter(H, u->fft_size);
+ if(channel == u->channels){
+ for(size_t c = 1; c < u->channels; ++c){
+ unsigned b_i = pa_aupdate_write_begin(u->a_H[c]);
+ float *H_p = u->Hs[c][b_i];
+ u->Xs[c][b_i] = preamp;
+ memcpy(H_p, H, FILTER_SIZE * sizeof(float));
+ pa_aupdate_write_end(u->a_H[c]);
+ }
+ }
+ pa_aupdate_write_end(u->a_H[r_channel]);
+ pa_xfree(ys);
+
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+ pa_assert_se((signal = dbus_message_new_signal(u->dbus_path, EQUALIZER_IFACE, equalizer_signals[EQUALIZER_SIGNAL_FILTER_CHANGED].name)));
+ pa_dbus_protocol_send_signal(u->dbus_protocol, signal);
+ dbus_message_unref(signal);
+}
+
+void equalizer_handle_get_filter_points(DBusConnection *conn, DBusMessage *msg, void *_u) {
+ struct userdata *u = (struct userdata *) _u;
+ uint32_t *xs, channel, r_channel;
+ double *ys, preamp;
+ unsigned x_npoints, a_i;
+ float *H;
+ pa_bool_t points_good=TRUE;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusError error;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(u);
+
+ dbus_error_init(&error);
+ if(!dbus_message_get_args(msg, &error,
+ DBUS_TYPE_UINT32, &channel,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &xs, &x_npoints,
+ DBUS_TYPE_INVALID)){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message);
+ dbus_error_free(&error);
+ return;
+ }
+ if(channel > u->channels){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel);
+ dbus_error_free(&error);
+ return;
+ }
+
+ for(size_t i = 0; i < x_npoints; ++i){
+ if(xs[i] >= FILTER_SIZE){
+ points_good=FALSE;
+ break;
+ }
+ }
+
+ if(x_npoints > FILTER_SIZE || !points_good){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs indices/length must be <= %ld!", FILTER_SIZE);
+ dbus_error_free(&error);
+ return;
+ }
+
+ r_channel = channel == u->channels ? 0 : channel;
+ ys = pa_xmalloc(x_npoints * sizeof(double));
+ a_i = pa_aupdate_read_begin(u->a_H[r_channel]);
+ H = u->Hs[r_channel][a_i];
+ preamp = u->Xs[r_channel][a_i];
+ for(uint32_t i = 0; i < x_npoints; ++i){
+ ys[i] = H[xs[i]] * u->fft_size;
+ }
+ pa_aupdate_read_end(u->a_H[r_channel]);
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+ dbus_message_iter_init_append(reply, &msg_iter);
+
+ pa_dbus_append_basic_array(&msg_iter, DBUS_TYPE_DOUBLE, ys, x_npoints);
+ pa_dbus_append_basic_variant(&msg_iter, DBUS_TYPE_DOUBLE, &preamp);
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+ dbus_message_unref(reply);
+ pa_xfree(ys);
+}
+
+static void get_filter(struct userdata *u, size_t channel, double **H_, double *preamp){
+ float *H;
+ unsigned a_i;
+ size_t r_channel = channel == u->channels ? 0 : channel;
+ *H_ = pa_xnew0(double, FILTER_SIZE);
+ a_i = pa_aupdate_read_begin(u->a_H[r_channel]);
+ H = u->Hs[r_channel][a_i];
+ for(size_t i = 0;i < FILTER_SIZE; ++i){
+ (*H_)[i] = H[i] * u->fft_size;
+ }
+ *preamp = u->Xs[r_channel][a_i];
+
+ pa_aupdate_read_end(u->a_H[r_channel]);
+}
+
+void equalizer_handle_get_filter(DBusConnection *conn, DBusMessage *msg, void *_u){
+ struct userdata *u;
+ unsigned n_coefs;
+ uint32_t channel;
+ double *H_, preamp;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusError error;
+ pa_assert_se(u = (struct userdata *) _u);
+ pa_assert(conn);
+ pa_assert(msg);
+
+ dbus_error_init(&error);
+ if(!dbus_message_get_args(msg, &error,
+ DBUS_TYPE_UINT32, &channel,
+ DBUS_TYPE_INVALID)){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message);
+ dbus_error_free(&error);
+ return;
+ }
+ if(channel > u->channels){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel);
+ dbus_error_free(&error);
+ return;
+ }
+
+ n_coefs = CHANNEL_PROFILE_SIZE;
+ pa_assert(conn);
+ pa_assert(msg);
+ get_filter(u, channel, &H_, &preamp);
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+ dbus_message_iter_init_append(reply, &msg_iter);
+
+ pa_dbus_append_basic_array(&msg_iter, DBUS_TYPE_DOUBLE, H_, n_coefs);
+ pa_dbus_append_basic_variant(&msg_iter, DBUS_TYPE_DOUBLE, &preamp);
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+ dbus_message_unref(reply);
+ pa_xfree(H_);
+}
+
+static void set_filter(struct userdata *u, size_t channel, double *H_, double preamp){
+ unsigned a_i;
+ size_t r_channel = channel == u->channels ? 0 : channel;
+ float *H;
+ //all channels
+ a_i = pa_aupdate_write_begin(u->a_H[r_channel]);
+ u->Xs[r_channel][a_i] = (float) preamp;
+ H = u->Hs[r_channel][a_i];
+ for(size_t i = 0; i < FILTER_SIZE; ++i){
+ H[i] = (float) H_[i];
+ }
+ fix_filter(H, u->fft_size);
+ if(channel == u->channels){
+ for(size_t c = 1; c < u->channels; ++c){
+ unsigned b_i = pa_aupdate_write_begin(u->a_H[c]);
+ u->Xs[c][b_i] = u->Xs[r_channel][a_i];
+ memcpy(u->Hs[c][b_i], u->Hs[r_channel][a_i], FILTER_SIZE * sizeof(float));
+ pa_aupdate_write_end(u->a_H[c]);
+ }
+ }
+ pa_aupdate_write_end(u->a_H[r_channel]);
+}
+
+void equalizer_handle_set_filter(DBusConnection *conn, DBusMessage *msg, void *_u){
+ struct userdata *u;
+ double *H, preamp;
+ uint32_t channel;
+ unsigned _n_coefs;
+ DBusMessage *signal = NULL;
+ DBusError error;
+ pa_assert_se(u = (struct userdata *) _u);
+ pa_assert(conn);
+ pa_assert(msg);
+
+ dbus_error_init(&error);
+ if(!dbus_message_get_args(msg, &error,
+ DBUS_TYPE_UINT32, &channel,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_DOUBLE, &H, &_n_coefs,
+ DBUS_TYPE_DOUBLE, &preamp,
+ DBUS_TYPE_INVALID)){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message);
+ dbus_error_free(&error);
+ return;
+ }
+ if(channel > u->channels){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel);
+ dbus_error_free(&error);
+ return;
+ }
+ if(_n_coefs != FILTER_SIZE){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "This filter takes exactly %ld coefficients, you gave %d", FILTER_SIZE, _n_coefs);
+ return;
+ }
+ set_filter(u, channel, H, preamp);
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+ pa_assert_se((signal = dbus_message_new_signal(u->dbus_path, EQUALIZER_IFACE, equalizer_signals[EQUALIZER_SIGNAL_FILTER_CHANGED].name)));
+ pa_dbus_protocol_send_signal(u->dbus_protocol, signal);
+ dbus_message_unref(signal);
+}
+
+void equalizer_handle_save_profile(DBusConnection *conn, DBusMessage *msg, void *_u) {
+ struct userdata *u = (struct userdata *) _u;
+ char *name;
+ uint32_t channel, r_channel;
+ DBusMessage *signal = NULL;
+ DBusError error;
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(u);
+ dbus_error_init(&error);
+
+ if(!dbus_message_get_args(msg, &error,
+ DBUS_TYPE_UINT32, &channel,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message);
+ dbus_error_free(&error);
+ return;
+ }
+ if(channel > u->channels){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel);
+ dbus_error_free(&error);
+ return;
+ }
+ r_channel = channel == u->channels ? 0 : channel;
+ save_profile(u, r_channel, name);
+ pa_dbus_send_empty_reply(conn, msg);
+
+ pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_PROFILES_CHANGED].name)));
+ pa_dbus_protocol_send_signal(u->dbus_protocol, signal);
+ dbus_message_unref(signal);
+}
+
+void equalizer_handle_load_profile(DBusConnection *conn, DBusMessage *msg, void *_u) {
+ struct userdata *u = (struct userdata *) _u;
+ char *name;
+ DBusError error;
+ uint32_t channel, r_channel;
+ const char *err_msg = NULL;
+ DBusMessage *signal = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(u);
+ dbus_error_init(&error);
+
+ if(!dbus_message_get_args(msg, &error,
+ DBUS_TYPE_UINT32, &channel,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message);
+ dbus_error_free(&error);
+ return;
+ }
+ if(channel > u->channels){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel);
+ dbus_error_free(&error);
+ return;
+ }
+ r_channel = channel == u->channels ? 0 : channel;
+
+ err_msg = load_profile(u, r_channel, name);
+ if(err_msg != NULL){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "error loading profile %s: %s", name, err_msg);
+ dbus_error_free(&error);
+ return;
+ }
+ if(channel == u->channels){
+ for(uint32_t c = 1; c < u->channels; ++c){
+ load_profile(u, c, name);
+ }
+ }
+ pa_dbus_send_empty_reply(conn, msg);
+
+ pa_assert_se((signal = dbus_message_new_signal(u->dbus_path, EQUALIZER_IFACE, equalizer_signals[EQUALIZER_SIGNAL_FILTER_CHANGED].name)));
+ pa_dbus_protocol_send_signal(u->dbus_protocol, signal);
+ dbus_message_unref(signal);
+}
+
+void equalizer_handle_save_state(DBusConnection *conn, DBusMessage *msg, void *_u) {
+ struct userdata *u = (struct userdata *) _u;
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(u);
+
+ save_state(u);
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+void equalizer_handle_get_profile_name(DBusConnection *conn, DBusMessage *msg, void *_u){
+ struct userdata *u = (struct userdata *) _u;
+ DBusError error;
+ uint32_t channel, r_channel;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(u);
+ dbus_error_init(&error);
+
+ if(!dbus_message_get_args(msg, &error,
+ DBUS_TYPE_UINT32, &channel,
+ DBUS_TYPE_INVALID)){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message);
+ dbus_error_free(&error);
+ return;
+ }
+ if(channel > u->channels){
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel);
+ dbus_error_free(&error);
+ return;
+ }
+ r_channel = channel == u->channels ? 0 : channel;
+ pa_assert(u->base_profiles[r_channel]);
+ pa_dbus_send_basic_value_reply(conn,msg, DBUS_TYPE_STRING, &u->base_profiles[r_channel]);
+}
+
+void equalizer_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u){
+ uint32_t rev=1;
+ pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_UINT32, &rev);
+}
+
+void equalizer_get_n_channels(DBusConnection *conn, DBusMessage *msg, void *_u){
+ struct userdata *u;
+ uint32_t channels;
+ pa_assert_se(u = (struct userdata *) _u);
+ pa_assert(conn);
+ pa_assert(msg);
+
+ channels = (uint32_t) u->channels;
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &channels);
+}
+
+void equalizer_get_n_coefs(DBusConnection *conn, DBusMessage *msg, void *_u){
+ struct userdata *u;
+ uint32_t n_coefs;
+ pa_assert_se(u = (struct userdata *) _u);
+ pa_assert(conn);
+ pa_assert(msg);
+
+ n_coefs = (uint32_t) CHANNEL_PROFILE_SIZE;
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &n_coefs);
+}
+
+void equalizer_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *_u){
+ struct userdata *u;
+ uint32_t rate;
+ pa_assert_se(u = (struct userdata *) _u);
+ pa_assert(conn);
+ pa_assert(msg);
+
+ rate = (uint32_t) u->sink->sample_spec.rate;
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &rate);
+}
+
+void equalizer_get_filter_rate(DBusConnection *conn, DBusMessage *msg, void *_u){
+ struct userdata *u;
+ uint32_t fft_size;
+ pa_assert_se(u = (struct userdata *) _u);
+ pa_assert(conn);
+ pa_assert(msg);
+
+ fft_size = (uint32_t) u->fft_size;
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &fft_size);
+}
+
+void equalizer_get_all(DBusConnection *conn, DBusMessage *msg, void *_u){
+ struct userdata *u;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter, dict_iter;
+ uint32_t rev, n_coefs, rate, fft_size, channels;
+ pa_assert_se(u = (struct userdata *) _u);
+ pa_assert(msg);
+
+ rev = 1;
+ n_coefs = (uint32_t) CHANNEL_PROFILE_SIZE;
+ rate = (uint32_t) u->sink->sample_spec.rate;
+ fft_size = (uint32_t) u->fft_size;
+ channels = (uint32_t) u->channels;
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_REVISION].property_name, DBUS_TYPE_UINT32, &rev);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_SAMPLERATE].property_name, DBUS_TYPE_UINT32, &rate);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_FILTERSAMPLERATE].property_name, DBUS_TYPE_UINT32, &fft_size);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_N_COEFS].property_name, DBUS_TYPE_UINT32, &n_coefs);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_N_CHANNELS].property_name, DBUS_TYPE_UINT32, &channels);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+ dbus_message_unref(reply);
+}
diff --git a/src/modules/module-hal-detect-compat.c b/src/modules/module-hal-detect-compat.c
new file mode 100644
index 00000000..14cf8143
--- /dev/null
+++ b/src/modules/module-hal-detect-compat.c
@@ -0,0 +1,84 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+
+#include "module-hal-detect-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Compatibility module");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!");
+
+static const char* const valid_modargs[] = {
+ "api",
+ "tsched",
+ "subdevices",
+ NULL,
+};
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ pa_bool_t tsched = TRUE;
+ pa_module *n;
+ char *t;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "tsched", &tsched) < 0) {
+ pa_log("tsched= expects boolean arguments");
+ goto fail;
+ }
+
+ pa_log_warn("We will now load module-udev-detect. Please make sure to remove module-hal-detect from your configuration.");
+
+ t = pa_sprintf_malloc("tsched=%s", pa_yes_no(tsched));
+ n = pa_module_load(m->core, "module-udev-detect", t);
+ pa_xfree(t);
+
+ if (n)
+ pa_module_unload_request(m, TRUE);
+
+ pa_modargs_free(ma);
+
+ return n ? 0 : -1;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c
index 79758b92..18519131 100644
--- a/src/modules/module-hal-detect.c
+++ b/src/modules/module-hal-detect.c
@@ -55,16 +55,16 @@ PA_MODULE_AUTHOR("Shahms King");
PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(TRUE);
-#if defined(HAVE_ALSA) && defined(HAVE_OSS)
+#if defined(HAVE_ALSA) && defined(HAVE_OSS_OUTPUT)
PA_MODULE_USAGE("api=<alsa or oss> "
"tsched=<enable system timer based scheduling mode?>"
- "subdevs=<init all subdevices>");
+ "subdevices=<init all subdevices>");
#elif defined(HAVE_ALSA)
PA_MODULE_USAGE("api=<alsa> "
"tsched=<enable system timer based scheduling mode?>");
-#elif defined(HAVE_OSS)
+#elif defined(HAVE_OSS_OUTPUT)
PA_MODULE_USAGE("api=<oss>"
- "subdevs=<init all subdevices>");
+ "subdevices=<init all subdevices>");
#endif
PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!");
@@ -84,7 +84,7 @@ struct userdata {
#ifdef HAVE_ALSA
pa_bool_t use_tsched;
#endif
-#ifdef HAVE_OSS
+#ifdef HAVE_OSS_OUTPUT
pa_bool_t init_subdevs;
#endif
};
@@ -97,8 +97,8 @@ static const char* const valid_modargs[] = {
#ifdef HAVE_ALSA
"tsched",
#endif
-#ifdef HAVE_OSS
- "subdevs",
+#ifdef HAVE_OSS_OUTPUT
+ "subdevices",
#endif
NULL
};
@@ -270,7 +270,7 @@ fail:
#endif
-#ifdef HAVE_OSS
+#ifdef HAVE_OSS_OUTPUT
static pa_bool_t hal_oss_device_is_pcm(LibHalContext *context, const char *udi, pa_bool_t init_subdevices) {
char *class = NULL, *dev = NULL, *e;
@@ -402,7 +402,7 @@ static struct device* hal_device_add(struct userdata *u, const char *udi) {
if (pa_streq(u->capability, CAPABILITY_ALSA))
r = hal_device_load_alsa(u, udi, d);
#endif
-#ifdef HAVE_OSS
+#ifdef HAVE_OSS_OUTPUT
if (pa_streq(u->capability, CAPABILITY_OSS))
r = hal_device_load_oss(u, udi, d);
#endif
@@ -435,9 +435,7 @@ static int hal_device_add_all(struct userdata *u) {
int i;
for (i = 0; i < n; i++) {
- struct device *d;
-
- if ((d = hal_device_add(u, udis[i]))) {
+ if (hal_device_add(u, udis[i])) {
count++;
pa_log_debug("Loaded device %s", udis[i]);
} else
@@ -623,8 +621,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
}
- return DBUS_HANDLER_RESULT_HANDLED;
-
} else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
/* We use this message to avoid a dirty race condition when we
get an ACLAdded message before the previously owning PA
@@ -668,7 +664,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
/* Yes, we don't check the UDI for validity, but hopefully HAL will */
device_added_cb(u->context, udi);
- return DBUS_HANDLER_RESULT_HANDLED;
}
finish:
@@ -761,7 +756,7 @@ int pa__init(pa_module*m) {
api = pa_modargs_get_value(ma, "api", "oss");
#endif
-#ifdef HAVE_OSS
+#ifdef HAVE_OSS_OUTPUT
if (pa_streq(api, "oss"))
u->capability = CAPABILITY_OSS;
#endif
@@ -771,9 +766,9 @@ int pa__init(pa_module*m) {
goto fail;
}
-#ifdef HAVE_OSS
- if (pa_modargs_get_value_boolean(ma, "subdevs", &u->init_subdevs) < 0) {
- pa_log("Failed to parse subdevs argument.");
+#ifdef HAVE_OSS_OUTPUT
+ if (pa_modargs_get_value_boolean(ma, "subdevices", &u->init_subdevs) < 0) {
+ pa_log("Failed to parse subdevices= argument.");
goto fail;
}
#endif
diff --git a/src/modules/module-intended-roles.c b/src/modules/module-intended-roles.c
index c697209a..b9924dfd 100644
--- a/src/modules/module-intended-roles.c
+++ b/src/modules/module-intended-roles.c
@@ -127,6 +127,9 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
if (s == def)
continue;
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)))
+ continue;
+
if (role_match(s->proplist, role)) {
new_data->sink = s;
new_data->save_sink = FALSE;
@@ -173,6 +176,9 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou
if (s == def)
continue;
+ if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)))
+ continue;
+
if (role_match(s->proplist, role)) {
new_data->source = s;
new_data->save_source = FALSE;
@@ -201,6 +207,17 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct
if (si->save_sink)
continue;
+ /* Skip this if it is already in the process of being moved
+ * anyway */
+ if (!si->sink)
+ continue;
+
+ /* It might happen that a stream and a sink are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si)))
+ continue;
+
if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE)))
continue;
@@ -237,6 +254,17 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source,
if (so->direct_on_input)
continue;
+ /* Skip this if it is already in the process of being moved
+ * anyway */
+ if (!so->source)
+ continue;
+
+ /* It might happen that a stream and a source are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so)))
+ continue;
+
if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE)))
continue;
@@ -275,24 +303,28 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str
uint32_t jdx;
pa_sink *d;
+ if (!si->sink)
+ continue;
+
if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE)))
continue;
/* Would the default sink fit? If so, let's use it */
- if (def != sink && role_match(def->proplist, role)) {
- pa_sink_input_move_to(si, def, FALSE);
- continue;
- }
+ if (def != sink && role_match(def->proplist, role))
+ if (pa_sink_input_move_to(si, def, FALSE) >= 0)
+ continue;
/* Try to find some other fitting sink */
PA_IDXSET_FOREACH(d, c->sinks, jdx) {
if (d == def || d == sink)
continue;
- if (role_match(d->proplist, role)) {
- pa_sink_input_move_to(si, d, FALSE);
- break;
- }
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(d)))
+ continue;
+
+ if (role_match(d->proplist, role))
+ if (pa_sink_input_move_to(si, d, FALSE) >= 0)
+ break;
}
}
@@ -325,6 +357,9 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc
if (so->direct_on_input)
continue;
+ if (!so->source)
+ continue;
+
if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE)))
continue;
@@ -339,6 +374,9 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc
if (d == def || d == source)
continue;
+ if (!PA_SOURCE_IS_LINKED(pa_source_get_state(d)))
+ continue;
+
if (role_match(d->proplist, role) && !source->monitor_of == !d->monitor_of) {
pa_source_output_move_to(so, d, FALSE);
break;
diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c
index 21f4a8f1..994c778f 100644
--- a/src/modules/module-ladspa-sink.c
+++ b/src/modules/module-ladspa-sink.c
@@ -64,10 +64,9 @@ PA_MODULE_USAGE(
#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
struct userdata {
- pa_core *core;
pa_module *module;
- pa_sink *sink, *master;
+ pa_sink *sink;
pa_sink_input *sink_input;
const LADSPA_Descriptor *descriptor;
@@ -83,6 +82,8 @@ struct userdata {
LADSPA_Data control_out;
pa_memblockq *memblockq;
+
+ pa_bool_t auto_desc;
};
static const char* const valid_modargs[] = {
@@ -100,69 +101,111 @@ static const char* const valid_modargs[] = {
};
/* Called from I/O thread context */
-static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
switch (code) {
- case PA_SINK_MESSAGE_GET_LATENCY: {
- pa_usec_t usec = 0;
+ case PA_SINK_MESSAGE_GET_LATENCY:
+
+ /* The sink is _put() before the sink input is, so let's
+ * make sure we don't access it in that time. Also, the
+ * sink input is first shut down, the sink second. */
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
+ *((pa_usec_t*) data) = 0;
+ return 0;
+ }
+
+ *((pa_usec_t*) data) =
- /* Get the latency of the master sink */
- if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
- usec = 0;
+ /* Get the latency of the master sink */
+ pa_sink_get_latency_within_thread(u->sink_input->sink) +
- /* Add the latency internal to our sink input on top */
- usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec);
+ /* Add the latency internal to our sink input on top */
+ pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
- *((pa_usec_t*) data) = usec;
return 0;
- }
}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
/* Called from main context */
-static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
+static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
- if (PA_SINK_IS_LINKED(state) &&
- u->sink_input &&
- PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
-
- pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
+ if (!PA_SINK_IS_LINKED(state) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return 0;
+ pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
return 0;
}
/* Called from I/O thread context */
-static void sink_request_rewind(pa_sink *s) {
+static void sink_request_rewind_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+ return;
+
/* Just hand this one over to the master sink */
pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq), TRUE, FALSE, FALSE);
}
/* Called from I/O thread context */
-static void sink_update_requested_latency(pa_sink *s) {
+static void sink_update_requested_latency_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+ return;
+
/* Just hand this one over to the master sink */
pa_sink_input_set_requested_latency_within_thread(
u->sink_input,
pa_sink_get_requested_latency_within_thread(s));
}
+/* Called from main context */
+static void sink_set_volume_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return;
+
+ pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE);
+}
+
+/* Called from main context */
+static void sink_set_mute_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return;
+
+ pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
+}
+
/* Called from I/O thread context */
static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
struct userdata *u;
@@ -175,8 +218,8 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
pa_assert(chunk);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
- return -1;
+ /* Hmm, process any rewind request that might be queued up */
+ pa_sink_process_rewind(u->sink, 0);
while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) {
pa_memchunk nchunk;
@@ -225,9 +268,6 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
- return;
-
if (u->sink->thread_info.rewind_nbytes > 0) {
size_t max_rewrite;
@@ -263,9 +303,6 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
- return;
-
pa_memblockq_set_maxrewind(u->memblockq, nbytes);
pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
}
@@ -277,9 +314,6 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
- return;
-
pa_sink_set_max_request_within_thread(u->sink, nbytes);
}
@@ -290,24 +324,28 @@ static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
- return;
-
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
}
/* Called from I/O thread context */
-static void sink_input_detach_cb(pa_sink_input *i) {
+static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
- return;
+ pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+}
+
+/* Called from I/O thread context */
+static void sink_input_detach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
pa_sink_detach_within_thread(u->sink);
- pa_sink_set_asyncmsgq(u->sink, NULL);
+
pa_sink_set_rtpoll(u->sink, NULL);
}
@@ -318,14 +356,13 @@ static void sink_input_attach_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
- return;
+ pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
+ pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+ pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+ pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i));
+ pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
- pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq);
- pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);
pa_sink_attach_within_thread(u->sink);
-
- pa_sink_set_latency_range_within_thread(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency);
}
/* Called from main context */
@@ -335,14 +372,18 @@ static void sink_input_kill_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- pa_sink_unlink(u->sink);
+ /* The order here matters! We first kill the sink input, followed
+ * by the sink. That means the sink callbacks must be protected
+ * against an unconnected sink input! */
pa_sink_input_unlink(u->sink_input);
+ pa_sink_unlink(u->sink);
- pa_sink_unref(u->sink);
- u->sink = NULL;
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+
pa_module_unload_request(u->module, TRUE);
}
@@ -372,13 +413,59 @@ static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
return u->sink != dest;
}
+/* Called from main context */
+static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ if (dest) {
+ pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
+ pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
+ } else
+ pa_sink_set_asyncmsgq(u->sink, NULL);
+
+ if (u->auto_desc && dest) {
+ const char *z;
+ pa_proplist *pl;
+
+ pl = pa_proplist_new();
+ z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s",
+ pa_proplist_gets(u->sink->proplist, "device.ladspa.name"), z ? z : dest->name);
+
+ pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
+ pa_proplist_free(pl);
+ }
+}
+
+/* Called from main context */
+static void sink_input_volume_changed_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_volume_changed(u->sink, &i->volume);
+}
+
+/* Called from main context */
+static void sink_input_mute_changed_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_mute_changed(u->sink, i->muted);
+}
+
int pa__init(pa_module*m) {
struct userdata *u;
pa_sample_spec ss;
pa_channel_map map;
pa_modargs *ma;
char *t;
- const char *z;
pa_sink *master;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
@@ -392,7 +479,7 @@ int pa__init(pa_module*m) {
pa_assert(m);
- pa_assert(sizeof(LADSPA_Data) == sizeof(float));
+ pa_assert_cc(sizeof(LADSPA_Data) == sizeof(float));
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log("Failed to parse module arguments.");
@@ -425,12 +512,8 @@ int pa__init(pa_module*m) {
cdata = pa_modargs_get_value(ma, "control", NULL);
u = pa_xnew0(struct userdata, 1);
- u->core = m->core;
u->module = m;
m->userdata = u;
- u->master = master;
- u->sink = NULL;
- u->sink_input = NULL;
u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL);
if (!(e = getenv("LADSPA_PATH")))
@@ -694,11 +777,8 @@ int pa__init(pa_module*m) {
sink_data.module = m;
if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
sink_data.name = pa_sprintf_malloc("%s.ladspa", master->name);
- sink_data.namereg_fail = FALSE;
pa_sink_new_data_set_sample_spec(&sink_data, &ss);
pa_sink_new_data_set_channel_map(&sink_data, &map);
- z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
- pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", label, z ? z : master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
pa_proplist_sets(sink_data.proplist, "device.ladspa.module", plugin);
@@ -714,7 +794,16 @@ int pa__init(pa_module*m) {
goto fail;
}
- u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY);
+ if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
+ const char *z;
+
+ z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", d->Name, z ? z : master->name);
+ }
+
+ u->sink = pa_sink_new(m->core, &sink_data,
+ PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME|
+ (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
pa_sink_new_data_done(&sink_data);
if (!u->sink) {
@@ -722,26 +811,27 @@ int pa__init(pa_module*m) {
goto fail;
}
- u->sink->parent.process_msg = sink_process_msg;
- u->sink->set_state = sink_set_state;
- u->sink->update_requested_latency = sink_update_requested_latency;
- u->sink->request_rewind = sink_request_rewind;
+ u->sink->parent.process_msg = sink_process_msg_cb;
+ u->sink->set_state = sink_set_state_cb;
+ u->sink->update_requested_latency = sink_update_requested_latency_cb;
+ u->sink->request_rewind = sink_request_rewind_cb;
+ u->sink->set_volume = sink_set_volume_cb;
+ u->sink->set_mute = sink_set_mute_cb;
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
- pa_sink_set_rtpoll(u->sink, master->rtpoll);
/* Create sink input */
pa_sink_input_new_data_init(&sink_input_data);
sink_input_data.driver = __FILE__;
sink_input_data.module = m;
- sink_input_data.sink = u->master;
+ sink_input_data.sink = master;
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream");
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
- pa_sink_input_new(&u->sink_input, m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE);
+ pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
pa_sink_input_new_data_done(&sink_input_data);
if (!u->sink_input)
@@ -752,11 +842,15 @@ int pa__init(pa_module*m) {
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
u->sink_input->update_max_request = sink_input_update_max_request_cb;
u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
+ u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
u->sink_input->kill = sink_input_kill_cb;
u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb;
u->sink_input->state_change = sink_input_state_change_cb;
u->sink_input->may_move_to = sink_input_may_move_to_cb;
+ u->sink_input->moving = sink_input_moving_cb;
+ u->sink_input->volume_changed = sink_input_volume_changed_cb;
+ u->sink_input->mute_changed = sink_input_mute_changed_cb;
u->sink_input->userdata = u;
pa_sink_put(u->sink);
@@ -797,15 +891,20 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
- if (u->sink) {
- pa_sink_unlink(u->sink);
- pa_sink_unref(u->sink);
- }
+ /* See comments in sink_input_kill_cb() above regarding
+ * destruction order! */
- if (u->sink_input) {
+ if (u->sink_input)
pa_sink_input_unlink(u->sink_input);
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->sink_input)
pa_sink_input_unref(u->sink_input);
- }
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
for (c = 0; c < u->channels; c++)
if (u->handle[c]) {
diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c
index 06efeb8f..e9778620 100644
--- a/src/modules/module-lirc.c
+++ b/src/modules/module-lirc.c
@@ -45,12 +45,14 @@ PA_MODULE_AUTHOR("Lennart Poettering");
PA_MODULE_DESCRIPTION("LIRC volume control");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(TRUE);
-PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name>");
+PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name> volume_limit=<volume limit> volume_step=<volume change step>");
static const char* const valid_modargs[] = {
"config",
"sink",
"appname",
+ "volume_limit",
+ "volume_step",
NULL,
};
@@ -61,6 +63,8 @@ struct userdata {
char *sink_name;
pa_module *module;
float mute_toggle_save;
+ pa_volume_t volume_limit;
+ pa_volume_t volume_step;
};
static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) {
@@ -119,32 +123,17 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK)))
pa_log("Failed to get sink '%s'", u->sink_name);
else {
- int i;
- pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE);
-
-#define DELTA (PA_VOLUME_NORM/20)
+ pa_cvolume cv = *pa_sink_get_volume(s, FALSE);
switch (volchange) {
case UP:
- for (i = 0; i < cv.channels; i++) {
- if (cv.values[i] < PA_VOLUME_MAX - DELTA)
- cv.values[i] += DELTA;
- else
- cv.values[i] = PA_VOLUME_MAX;
- }
-
- pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);
+ pa_cvolume_inc_clamp(&cv, u->volume_step, u->volume_limit);
+ pa_sink_set_volume(s, &cv, TRUE, TRUE);
break;
case DOWN:
- for (i = 0; i < cv.channels; i++) {
- if (cv.values[i] > DELTA)
- cv.values[i] -= DELTA;
- else
- cv.values[i] = PA_VOLUME_MUTED;
- }
-
- pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);
+ pa_cvolume_dec(&cv, u->volume_step);
+ pa_sink_set_volume(s, &cv, TRUE, TRUE);
break;
case MUTE:
@@ -156,7 +145,6 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
break;
case MUTE_TOGGLE:
-
pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE);
break;
@@ -184,6 +172,8 @@ fail:
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
+ pa_volume_t volume_limit = PA_VOLUME_NORM*3/2;
+ pa_volume_t volume_step = PA_VOLUME_NORM/20;
pa_assert(m);
@@ -192,6 +182,16 @@ int pa__init(pa_module*m) {
goto fail;
}
+ if (pa_modargs_get_value_u32(ma, "volume_limit", &volume_limit) < 0) {
+ pa_log("Failed to parse volume limit");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_u32(ma, "volume_step", &volume_step) < 0) {
+ pa_log("Failed to parse volume step");
+ goto fail;
+ }
+
m->userdata = u = pa_xnew(struct userdata, 1);
u->module = m;
u->io = NULL;
@@ -199,6 +199,8 @@ int pa__init(pa_module*m) {
u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
u->lirc_fd = -1;
u->mute_toggle_save = 0;
+ u->volume_limit = volume_limit;
+ u->volume_step = volume_step;
if ((u->lirc_fd = lirc_init((char*) pa_modargs_get_value(ma, "appname", "pulseaudio"), 1)) < 0) {
pa_log("lirc_init() failed.");
diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
new file mode 100644
index 00000000..bb0182b0
--- /dev/null
+++ b/src/modules/module-loopback.c
@@ -0,0 +1,784 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Intel Corporation
+ Contributor: Pierre-Louis Bossart <pierre-louis.bossart@intel.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/sink-input.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+
+#include "module-loopback-symdef.h"
+
+PA_MODULE_AUTHOR("Pierre-Louis Bossart");
+PA_MODULE_DESCRIPTION("Loopback from source to sink");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "source=<source to connect to> "
+ "sink=<sink to connect to> "
+ "adjust_time=<how often to readjust rates in s> "
+ "latency_msec=<latency in ms> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map>");
+
+#define DEFAULT_LATENCY_MSEC 200
+
+#define MEMBLOCKQ_MAXLENGTH (1024*1024*16)
+
+#define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC)
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+
+ pa_sink_input *sink_input;
+ pa_source_output *source_output;
+
+ pa_asyncmsgq *asyncmsgq;
+ pa_memblockq *memblockq;
+
+ pa_rtpoll_item *rtpoll_item_read, *rtpoll_item_write;
+
+ pa_time_event *time_event;
+ pa_usec_t adjust_time;
+
+ int64_t recv_counter;
+ int64_t send_counter;
+
+ size_t skip;
+ pa_usec_t latency;
+
+ pa_bool_t in_pop;
+ size_t min_memblockq_length;
+
+ struct {
+ int64_t send_counter;
+ size_t source_output_buffer;
+ pa_usec_t source_latency;
+
+ int64_t recv_counter;
+ size_t sink_input_buffer;
+ pa_usec_t sink_latency;
+
+ size_t min_memblockq_length;
+ size_t max_request;
+ } latency_snapshot;
+};
+
+static const char* const valid_modargs[] = {
+ "source",
+ "sink",
+ "latency_msec",
+ "format",
+ "rate",
+ "channels",
+ "channel_map",
+ NULL,
+};
+
+enum {
+ SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX,
+ SINK_INPUT_MESSAGE_REWIND,
+ SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT,
+ SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED
+};
+
+enum {
+ SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT
+};
+
+/* Called from main context */
+static void teardown(struct userdata *u) {
+ pa_assert(u);
+ pa_assert_ctl_context();
+
+ if (u->sink_input)
+ pa_sink_input_unlink(u->sink_input);
+
+ if (u->source_output)
+ pa_source_output_unlink(u->source_output);
+
+ if (u->sink_input) {
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+ }
+
+ if (u->source_output) {
+ pa_source_output_unref(u->source_output);
+ u->source_output = NULL;
+ }
+}
+
+/* Called from main context */
+static void adjust_rates(struct userdata *u) {
+ size_t buffer, fs;
+ uint32_t old_rate, base_rate, new_rate;
+ pa_usec_t buffer_latency;
+
+ pa_assert(u);
+ pa_assert_ctl_context();
+
+ pa_asyncmsgq_send(u->source_output->source->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT, NULL, 0, NULL);
+ pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, NULL, 0, NULL);
+
+ buffer =
+ u->latency_snapshot.sink_input_buffer +
+ u->latency_snapshot.source_output_buffer;
+
+ if (u->latency_snapshot.recv_counter <= u->latency_snapshot.send_counter)
+ buffer += (size_t) (u->latency_snapshot.send_counter - u->latency_snapshot.recv_counter);
+ else
+ buffer += PA_CLIP_SUB(buffer, (size_t) (u->latency_snapshot.recv_counter - u->latency_snapshot.send_counter));
+
+ buffer_latency = pa_bytes_to_usec(buffer, &u->sink_input->sample_spec);
+
+ pa_log_info("Loopback overall latency is %0.2f ms + %0.2f ms + %0.2f ms = %0.2f ms",
+ (double) u->latency_snapshot.sink_latency / PA_USEC_PER_MSEC,
+ (double) buffer_latency / PA_USEC_PER_MSEC,
+ (double) u->latency_snapshot.source_latency / PA_USEC_PER_MSEC,
+ ((double) u->latency_snapshot.sink_latency + buffer_latency + u->latency_snapshot.source_latency) / PA_USEC_PER_MSEC);
+
+ pa_log_info("Should buffer %zu bytes, buffered at minimum %zu bytes",
+ u->latency_snapshot.max_request*2,
+ u->latency_snapshot.min_memblockq_length);
+
+ fs = pa_frame_size(&u->sink_input->sample_spec);
+ old_rate = u->sink_input->sample_spec.rate;
+ base_rate = u->source_output->sample_spec.rate;
+
+ if (u->latency_snapshot.min_memblockq_length < u->latency_snapshot.max_request*2)
+ new_rate = base_rate - (((u->latency_snapshot.max_request*2 - u->latency_snapshot.min_memblockq_length) / fs) *PA_USEC_PER_SEC)/u->adjust_time;
+ else
+ new_rate = base_rate + (((u->latency_snapshot.min_memblockq_length - u->latency_snapshot.max_request*2) / fs) *PA_USEC_PER_SEC)/u->adjust_time;
+
+ pa_log_info("Old rate %lu Hz, new rate %lu Hz", (unsigned long) old_rate, (unsigned long) new_rate);
+
+ pa_sink_input_set_rate(u->sink_input, new_rate);
+
+ pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time);
+}
+
+/* Called from main context */
+static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(u);
+ pa_assert(a);
+ pa_assert(u->time_event == e);
+
+ adjust_rates(u);
+}
+
+/* Called from input thread context */
+static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
+ struct userdata *u;
+ pa_memchunk copy;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ if (u->skip > chunk->length) {
+ u->skip -= chunk->length;
+ return;
+ }
+
+ if (u->skip > 0) {
+ copy = *chunk;
+ copy.index += u->skip;
+ copy.length -= u->skip;
+ u->skip = 0;
+
+ chunk = &copy;
+ }
+
+ pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, chunk, NULL);
+ u->send_counter += (int64_t) chunk->length;
+}
+
+/* Called from input thread context */
+static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL);
+ u->send_counter -= (int64_t) nbytes;
+}
+
+/* Called from output thread context */
+static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE_OUTPUT(obj)->userdata;
+
+ switch (code) {
+
+ case SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT: {
+ size_t length;
+
+ length = pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq);
+
+ u->latency_snapshot.send_counter = u->send_counter;
+ u->latency_snapshot.source_output_buffer = u->source_output->thread_info.resampler ? pa_resampler_result(u->source_output->thread_info.resampler, length) : length;
+ u->latency_snapshot.source_latency = pa_source_get_latency_within_thread(u->source_output->source);
+
+ return 0;
+ }
+ }
+
+ return pa_source_output_process_msg(obj, code, data, offset, chunk);
+}
+
+/* Called from output thread context */
+static void source_output_attach_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ u->rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
+ o->source->thread_info.rtpoll,
+ PA_RTPOLL_LATE,
+ u->asyncmsgq);
+}
+
+/* Called from output thread context */
+static void source_output_detach_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ if (u->rtpoll_item_write) {
+ pa_rtpoll_item_free(u->rtpoll_item_write);
+ u->rtpoll_item_write = NULL;
+ }
+}
+
+/* Called from output thread context */
+static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ if (PA_SOURCE_OUTPUT_IS_LINKED(state) && o->thread_info.state == PA_SOURCE_OUTPUT_INIT) {
+
+ u->skip = pa_usec_to_bytes(PA_CLIP_SUB(pa_source_get_latency_within_thread(o->source),
+ u->latency),
+ &o->sample_spec);
+
+ pa_log_info("Skipping %lu bytes", (unsigned long) u->skip);
+ }
+}
+
+/* Called from main thread */
+static void source_output_kill_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert_se(u = o->userdata);
+
+ teardown(u);
+ pa_module_unload_request(u->module, TRUE);
+}
+
+/* Called from main thread */
+static pa_bool_t source_output_may_move_to_cb(pa_source_output *o, pa_source *dest) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert_se(u = o->userdata);
+
+ return dest != u->sink_input->sink->monitor_source;
+}
+
+/* Called from main thread */
+static void source_output_moving_cb(pa_source_output *o, pa_source *dest) {
+ pa_proplist *p;
+ const char *n;
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert_se(u = o->userdata);
+
+ p = pa_proplist_new();
+ pa_proplist_setf(p, PA_PROP_MEDIA_NAME, "Loopback of %s", pa_strnull(pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+
+ if ((n = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_ICON_NAME)))
+ pa_proplist_sets(p, PA_PROP_MEDIA_ICON_NAME, n);
+
+ pa_sink_input_update_proplist(u->sink_input, PA_UPDATE_REPLACE, p);
+ pa_proplist_free(p);
+}
+
+/* Called from output thread context */
+static void update_min_memblockq_length(struct userdata *u) {
+ size_t length;
+
+ pa_assert(u);
+ pa_sink_input_assert_io_context(u->sink_input);
+
+ length = pa_memblockq_get_length(u->memblockq);
+
+ if (u->min_memblockq_length == (size_t) -1 ||
+ length < u->min_memblockq_length)
+ u->min_memblockq_length = length;
+}
+
+/* Called from output thread context */
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert_se(u = i->userdata);
+ pa_assert(chunk);
+
+ u->in_pop = TRUE;
+ while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0)
+ ;
+ u->in_pop = FALSE;
+
+ if (pa_memblockq_peek(u->memblockq, chunk) < 0) {
+ pa_log_info("Coud not peek into queue");
+ return -1;
+ }
+
+ chunk->length = PA_MIN(chunk->length, nbytes);
+ pa_memblockq_drop(u->memblockq, chunk->length);
+
+ update_min_memblockq_length(u);
+
+ return 0;
+}
+
+/* Called from output thread context */
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_memblockq_rewind(u->memblockq, nbytes);
+}
+
+/* Called from output thread context */
+static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK_INPUT(obj)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
+ pa_usec_t *r = data;
+
+ pa_sink_input_assert_io_context(u->sink_input);
+
+ *r = pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &u->sink_input->sample_spec);
+
+ /* Fall through, the default handler will add in the extra
+ * latency added by the resampler */
+ break;
+ }
+
+ case SINK_INPUT_MESSAGE_POST:
+
+ pa_sink_input_assert_io_context(u->sink_input);
+
+ if (PA_SINK_IS_OPENED(u->sink_input->sink->thread_info.state))
+ pa_memblockq_push_align(u->memblockq, chunk);
+ else
+ pa_memblockq_flush_write(u->memblockq);
+
+ update_min_memblockq_length(u);
+
+ /* Is this the end of an underrun? Then let's start things
+ * right-away */
+ if (!u->in_pop &&
+ u->sink_input->thread_info.underrun_for > 0 &&
+ pa_memblockq_is_readable(u->memblockq)) {
+
+ pa_log_debug("Requesting rewind due to end of underrun.");
+ pa_sink_input_request_rewind(u->sink_input,
+ (size_t) (u->sink_input->thread_info.underrun_for == (size_t) -1 ? 0 : u->sink_input->thread_info.underrun_for),
+ FALSE, TRUE, FALSE);
+ }
+
+ u->recv_counter += (int64_t) chunk->length;
+
+ return 0;
+
+ case SINK_INPUT_MESSAGE_REWIND:
+
+ pa_sink_input_assert_io_context(u->sink_input);
+
+ if (PA_SINK_IS_OPENED(u->sink_input->sink->thread_info.state))
+ pa_memblockq_seek(u->memblockq, -offset, PA_SEEK_RELATIVE, TRUE);
+ else
+ pa_memblockq_flush_write(u->memblockq);
+
+ u->recv_counter -= offset;
+
+ update_min_memblockq_length(u);
+
+ return 0;
+
+ case SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT: {
+ size_t length;
+
+ update_min_memblockq_length(u);
+
+ length = pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq);
+
+ u->latency_snapshot.recv_counter = u->recv_counter;
+ u->latency_snapshot.sink_input_buffer =
+ pa_memblockq_get_length(u->memblockq) +
+ (u->sink_input->thread_info.resampler ? pa_resampler_request(u->sink_input->thread_info.resampler, length) : length);
+ u->latency_snapshot.sink_latency = pa_sink_get_latency_within_thread(u->sink_input->sink);
+
+ u->latency_snapshot.max_request = pa_sink_input_get_max_request(u->sink_input);
+
+ u->latency_snapshot.min_memblockq_length = u->min_memblockq_length;
+ u->min_memblockq_length = (size_t) -1;
+
+ return 0;
+ }
+
+ case SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED: {
+ /* This message is sent from the IO thread to the main
+ * thread! So don't be confused. All the user cases above
+ * are executed in thread context, but this one is not! */
+
+ pa_assert_ctl_context();
+
+ adjust_rates(u);
+ return 0;
+ }
+ }
+
+ return pa_sink_input_process_msg(obj, code, data, offset, chunk);
+}
+
+/* Called from output thread context */
+static void sink_input_attach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert_se(u = i->userdata);
+
+ u->rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
+ i->sink->thread_info.rtpoll,
+ PA_RTPOLL_LATE,
+ u->asyncmsgq);
+
+ pa_memblockq_set_prebuf(u->memblockq, pa_sink_input_get_max_request(i)*2);
+ pa_memblockq_set_maxrewind(u->memblockq, pa_sink_input_get_max_rewind(i));
+
+ u->min_memblockq_length = (size_t) -1;
+}
+
+/* Called from output thread context */
+static void sink_input_detach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert_se(u = i->userdata);
+
+ if (u->rtpoll_item_read) {
+ pa_rtpoll_item_free(u->rtpoll_item_read);
+ u->rtpoll_item_read = NULL;
+ }
+}
+
+/* Called from output thread context */
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_memblockq_set_maxrewind(u->memblockq, nbytes);
+}
+
+/* Called from output thread context */
+static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_memblockq_set_prebuf(u->memblockq, nbytes*2);
+ pa_log_info("Max request changed");
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED, NULL, 0, NULL, NULL);
+}
+
+/* Called from main thread */
+static void sink_input_kill_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert_se(u = i->userdata);
+
+ teardown(u);
+ pa_module_unload_request(u->module, TRUE);
+}
+
+/* Called from main thread */
+static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
+ struct userdata *u;
+ pa_proplist *p;
+ const char *n;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert_se(u = i->userdata);
+
+ p = pa_proplist_new();
+ pa_proplist_setf(p, PA_PROP_MEDIA_NAME, "Loopback to %s", pa_strnull(pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+
+ if ((n = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_ICON_NAME)))
+ pa_proplist_sets(p, PA_PROP_MEDIA_ICON_NAME, n);
+
+ pa_source_output_update_proplist(u->source_output, PA_UPDATE_REPLACE, p);
+ pa_proplist_free(p);
+}
+
+/* Called from main thread */
+static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert_se(u = i->userdata);
+
+ if (!u->source_output->source->monitor_of)
+ return TRUE;
+
+ return dest != u->source_output->source->monitor_of;
+}
+
+int pa__init(pa_module *m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ pa_sink *sink;
+ pa_sink_input_new_data sink_input_data;
+ pa_source *source;
+ pa_source_output_new_data source_output_data;
+ uint32_t latency_msec;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_memchunk silence;
+ uint32_t adjust_time_sec;
+ const char *n;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ if (!(source = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source", NULL), PA_NAMEREG_SOURCE))) {
+ pa_log("No such source.");
+ goto fail;
+ }
+
+ if (!(sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink", NULL), PA_NAMEREG_SINK))) {
+ pa_log("No such sink.");
+ goto fail;
+ }
+
+ ss = sink->sample_spec;
+ map = sink->channel_map;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("Invalid sample format specification or channel map");
+ goto fail;
+ }
+
+ latency_msec = DEFAULT_LATENCY_MSEC;
+ if (pa_modargs_get_value_u32(ma, "latency_msec", &latency_msec) < 0 || latency_msec < 1 || latency_msec > 2000) {
+ pa_log("Invalid latency specification");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ u->latency = (pa_usec_t) latency_msec * PA_USEC_PER_MSEC;
+
+ adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
+ if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) {
+ pa_log("Failed to parse adjust_time value");
+ goto fail;
+ }
+
+ if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC)
+ u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC;
+ else
+ u->adjust_time = DEFAULT_ADJUST_TIME_USEC;
+
+ pa_sink_input_new_data_init(&sink_input_data);
+ sink_input_data.driver = __FILE__;
+ sink_input_data.module = m;
+ sink_input_data.sink = sink;
+
+ pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Loopback of %s",
+ pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+ if ((n = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME)))
+ pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ICON_NAME, n);
+ pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "abstract");
+ pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
+ pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
+ sink_input_data.flags = PA_SINK_INPUT_VARIABLE_RATE;
+
+ pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
+ pa_sink_input_new_data_done(&sink_input_data);
+
+ if (!u->sink_input)
+ goto fail;
+
+ u->sink_input->parent.process_msg = sink_input_process_msg_cb;
+ u->sink_input->pop = sink_input_pop_cb;
+ u->sink_input->process_rewind = sink_input_process_rewind_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->attach = sink_input_attach_cb;
+ u->sink_input->detach = sink_input_detach_cb;
+ u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
+ u->sink_input->update_max_request = sink_input_update_max_request_cb;
+ u->sink_input->may_move_to = sink_input_may_move_to_cb;
+ u->sink_input->moving = sink_input_moving_cb;
+ u->sink_input->userdata = u;
+
+ pa_sink_input_set_requested_latency(u->sink_input, u->latency/3);
+
+ pa_source_output_new_data_init(&source_output_data);
+ source_output_data.driver = __FILE__;
+ source_output_data.module = m;
+ source_output_data.source = source;
+ pa_proplist_setf(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Loopback to %s",
+ pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+ if ((n = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME)))
+ pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ICON_NAME, n);
+ pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "abstract");
+ pa_source_output_new_data_set_sample_spec(&source_output_data, &ss);
+ pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
+
+ pa_source_output_new(&u->source_output, m->core, &source_output_data);
+ pa_source_output_new_data_done(&source_output_data);
+
+ if (!u->source_output)
+ goto fail;
+
+ u->source_output->parent.process_msg = source_output_process_msg_cb;
+ u->source_output->push = source_output_push_cb;
+ u->source_output->process_rewind = source_output_process_rewind_cb;
+ u->source_output->kill = source_output_kill_cb;
+ u->source_output->attach = source_output_attach_cb;
+ u->source_output->detach = source_output_detach_cb;
+ u->source_output->state_change = source_output_state_change_cb;
+ u->source_output->may_move_to = source_output_may_move_to_cb;
+ u->source_output->moving = source_output_moving_cb;
+ u->source_output->userdata = u;
+
+ pa_source_output_set_requested_latency(u->source_output, u->latency/3);
+
+ pa_sink_input_get_silence(u->sink_input, &silence);
+ u->memblockq = pa_memblockq_new(
+ 0, /* idx */
+ MEMBLOCKQ_MAXLENGTH, /* maxlength */
+ MEMBLOCKQ_MAXLENGTH, /* tlength */
+ pa_frame_size(&ss), /* base */
+ 0, /* prebuf */
+ 0, /* minreq */
+ 0, /* maxrewind */
+ &silence); /* silence frame */
+ pa_memblock_unref(silence.memblock);
+
+ u->asyncmsgq = pa_asyncmsgq_new(0);
+
+ pa_sink_input_put(u->sink_input);
+ pa_source_output_put(u->source_output);
+
+ if (u->adjust_time > 0)
+ u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u);
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ teardown(u);
+
+ if (u->memblockq)
+ pa_memblockq_free(u->memblockq);
+
+ if (u->asyncmsgq)
+ pa_asyncmsgq_unref(u->asyncmsgq);
+
+ if (u->time_event)
+ u->core->mainloop->time_free(u->time_event);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-match.c b/src/modules/module-match.c
index 625f2a8b..b1693f18 100644
--- a/src/modules/module-match.c
+++ b/src/modules/module-match.c
@@ -85,7 +85,7 @@ static int load_rules(struct userdata *u, const char *filename) {
pa_assert(u);
if (filename)
- f = fopen(fn = pa_xstrdup(filename), "r");
+ f = pa_fopen_cloexec(fn = pa_xstrdup(filename), "r");
else
f = pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn);
@@ -216,7 +216,7 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v
pa_cvolume cv;
pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume);
pa_cvolume_set(&cv, si->sample_spec.channels, r->volume);
- pa_sink_input_set_volume(si, &cv, TRUE, TRUE);
+ pa_sink_input_set_volume(si, &cv, TRUE, FALSE);
}
}
}
@@ -243,6 +243,9 @@ int pa__init(pa_module*m) {
if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0)
goto fail;
+ /* FIXME: Doing this asynchronously is just broken. This needs to
+ * use a hook! */
+
u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u);
pa_modargs_free(ma);
diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c
index b30fae51..193c1f40 100644
--- a/src/modules/module-mmkbd-evdev.c
+++ b/src/modules/module-mmkbd-evdev.c
@@ -48,13 +48,15 @@ PA_MODULE_AUTHOR("Lennart Poettering");
PA_MODULE_DESCRIPTION("Multimedia keyboard support via Linux evdev");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(FALSE);
-PA_MODULE_USAGE("device=<evdev device> sink=<sink name>");
+PA_MODULE_USAGE("device=<evdev device> sink=<sink name> volume_limit=<volume limit> volume_step=<volume change step>");
#define DEFAULT_DEVICE "/dev/input/event0"
static const char* const valid_modargs[] = {
"device",
"sink",
+ "volume_limit",
+ "volume_step",
NULL,
};
@@ -63,6 +65,8 @@ struct userdata {
pa_io_event *io;
char *sink_name;
pa_module *module;
+ pa_volume_t volume_limit;
+ pa_volume_t volume_step;
};
static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) {
@@ -85,14 +89,27 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
}
if (ev.type == EV_KEY && (ev.value == 1 || ev.value == 2)) {
- enum { INVALID, UP, DOWN, MUTE_TOGGLE } volchange = INVALID;
+ enum {
+ INVALID,
+ UP,
+ DOWN,
+ MUTE_TOGGLE
+ } volchange = INVALID;
pa_log_debug("Key code=%u, value=%u", ev.code, ev.value);
switch (ev.code) {
- case KEY_VOLUMEDOWN: volchange = DOWN; break;
- case KEY_VOLUMEUP: volchange = UP; break;
- case KEY_MUTE: volchange = MUTE_TOGGLE; break;
+ case KEY_VOLUMEDOWN:
+ volchange = DOWN;
+ break;
+
+ case KEY_VOLUMEUP:
+ volchange = UP;
+ break;
+
+ case KEY_MUTE:
+ volchange = MUTE_TOGGLE;
+ break;
}
if (volchange != INVALID) {
@@ -101,36 +118,20 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK)))
pa_log("Failed to get sink '%s'", u->sink_name);
else {
- int i;
- pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE);
-
-#define DELTA (PA_VOLUME_NORM/20)
+ pa_cvolume cv = *pa_sink_get_volume(s, FALSE);
switch (volchange) {
case UP:
- for (i = 0; i < cv.channels; i++) {
- if (cv.values[i] < PA_VOLUME_MAX - DELTA)
- cv.values[i] += DELTA;
- else
- cv.values[i] = PA_VOLUME_MAX;
- }
-
- pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);
+ pa_cvolume_inc_clamp(&cv, u->volume_step, u->volume_limit);
+ pa_sink_set_volume(s, &cv, TRUE, TRUE);
break;
case DOWN:
- for (i = 0; i < cv.channels; i++) {
- if (cv.values[i] > DELTA)
- cv.values[i] -= DELTA;
- else
- cv.values[i] = PA_VOLUME_MUTED;
- }
-
- pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);
+ pa_cvolume_dec(&cv, u->volume_step);
+ pa_sink_set_volume(s, &cv, TRUE, TRUE);
break;
case MUTE_TOGGLE:
-
pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE);
break;
@@ -161,6 +162,8 @@ int pa__init(pa_module*m) {
struct input_id input_id;
char name[256];
uint8_t evtype_bitmask[EV_MAX/8 + 1];
+ pa_volume_t volume_limit = PA_VOLUME_NORM*3/2;
+ pa_volume_t volume_step = PA_VOLUME_NORM/20;
pa_assert(m);
@@ -169,14 +172,26 @@ int pa__init(pa_module*m) {
goto fail;
}
+ if (pa_modargs_get_value_u32(ma, "volume_limit", &volume_limit) < 0) {
+ pa_log("Failed to parse volume limit");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_u32(ma, "volume_step", &volume_step) < 0) {
+ pa_log("Failed to parse volume step");
+ goto fail;
+ }
+
m->userdata = u = pa_xnew(struct userdata, 1);
u->module = m;
u->io = NULL;
u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
u->fd = -1;
u->fd_type = 0;
+ u->volume_limit = volume_limit;
+ u->volume_step = volume_step;
- if ((u->fd = open(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY|O_NOCTTY)) < 0) {
+ if ((u->fd = pa_open_cloexec(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY, 0)) < 0) {
pa_log("Failed to open evdev device: %s", pa_cstrerror(errno));
goto fail;
}
diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c
index 36c50b05..74a2ebb1 100644
--- a/src/modules/module-null-sink.c
+++ b/src/modules/module-null-sink.c
@@ -35,6 +35,7 @@
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/xmalloc.h>
+#include <pulse/i18n.h>
#include <pulsecore/macro.h>
#include <pulsecore/sink.h>
@@ -51,7 +52,7 @@
#include "module-null-sink-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering");
-PA_MODULE_DESCRIPTION("Clocked NULL sink");
+PA_MODULE_DESCRIPTION(_("Clocked NULL sink"));
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(FALSE);
PA_MODULE_USAGE(
@@ -287,7 +288,7 @@ int pa__init(pa_module*m) {
pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME));
pa_sink_new_data_set_sample_spec(&data, &ss);
pa_sink_new_data_set_channel_map(&data, &map);
- pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", "Null Output"));
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", _("Null Output")));
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract");
if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c
index 9c169327..10cc3415 100644
--- a/src/modules/module-pipe-sink.c
+++ b/src/modules/module-pipe-sink.c
@@ -253,12 +253,11 @@ int pa__init(pa_module*m) {
u->filename = pa_runtime_path(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME));
mkfifo(u->filename, 0666);
- if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) {
+ if ((u->fd = pa_open_cloexec(u->filename, O_RDWR, 0)) < 0) {
pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno));
goto fail;
}
- pa_make_fd_cloexec(u->fd);
pa_make_fd_nonblock(u->fd);
if (fstat(u->fd, &st) < 0) {
diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c
index 49104f8d..de680933 100644
--- a/src/modules/module-pipe-source.c
+++ b/src/modules/module-pipe-source.c
@@ -238,12 +238,11 @@ int pa__init(pa_module*m) {
u->filename = pa_runtime_path(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME));
mkfifo(u->filename, 0666);
- if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) {
+ if ((u->fd = pa_open_cloexec(u->filename, O_RDWR, 0)) < 0) {
pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno));
goto fail;
}
- pa_make_fd_cloexec(u->fd);
pa_make_fd_nonblock(u->fd);
if (fstat(u->fd, &st) < 0) {
diff --git a/src/modules/module-position-event-sounds.c b/src/modules/module-position-event-sounds.c
index e191ec33..ee4c8c88 100644
--- a/src/modules/module-position-event-sounds.c
+++ b/src/modules/module-position-event-sounds.c
@@ -57,35 +57,85 @@ struct userdata {
pa_hook_slot *sink_input_fixate_hook_slot;
};
+static int parse_pos(const char *pos, double *f) {
+
+ if (pa_atod(pos, f) < 0) {
+ pa_log_warn("Failed to parse hpos/vpos property '%s'.", pos);
+ return -1;
+ }
+
+ if (*f < 0.0 || *f > 1.0) {
+ pa_log_debug("Property hpos/vpos out of range %0.2f", *f);
+
+ *f = PA_CLAMP(*f, 0.0, 1.0);
+ }
+
+ return 0;
+}
+
static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *core, pa_sink_input_new_data *data, struct userdata *u) {
- const char *hpos;
+ const char *hpos, *vpos, *role, *id;
double f;
char t[PA_CVOLUME_SNPRINT_MAX];
pa_cvolume v;
pa_assert(data);
- if (!(hpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_HPOS)))
+ if (!(role = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ROLE)))
return PA_HOOK_OK;
- if (pa_atod(hpos, &f) < 0) {
- pa_log_warn("Failed to parse "PA_PROP_EVENT_MOUSE_HPOS" property '%s'.", hpos);
+ if (!pa_streq(role, "event"))
return PA_HOOK_OK;
+
+ if ((id = pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID))) {
+
+ /* The test sounds should never be positioned in space, since
+ * they might be trigered themselves to configure the speakers
+ * in space, which we don't want to mess up. */
+
+ if (pa_startswith(id, "audio-channel-"))
+ return PA_HOOK_OK;
+
+ if (pa_streq(id, "audio-volume-change"))
+ return PA_HOOK_OK;
+
+ if (pa_streq(id, "audio-test-signal"))
+ return PA_HOOK_OK;
}
- if (f < 0.0 || f > 1.0) {
- pa_log_warn("Property "PA_PROP_EVENT_MOUSE_HPOS" out of range %0.2f", f);
+ if (!(hpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_HPOS)))
+ hpos = pa_proplist_gets(data->proplist, PA_PROP_WINDOW_HPOS);
+
+ if (!(vpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_VPOS)))
+ vpos = pa_proplist_gets(data->proplist, PA_PROP_WINDOW_VPOS);
+
+ if (!hpos && !vpos)
return PA_HOOK_OK;
+
+ pa_cvolume_reset(&v, data->sink->sample_spec.channels);
+
+ if (hpos) {
+ if (parse_pos(hpos, &f) < 0)
+ return PA_HOOK_OK;
+
+ if (pa_channel_map_can_balance(&data->sink->channel_map)) {
+ pa_log_debug("Positioning event sound '%s' horizontally at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f);
+ pa_cvolume_set_balance(&v, &data->sink->channel_map, f*2.0-1.0);
+ }
}
- pa_log_debug("Positioning event sound '%s' at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f);
+ if (vpos) {
+ if (parse_pos(vpos, &f) < 0)
+ return PA_HOOK_OK;
- pa_cvolume_reset(&v, data->sample_spec.channels);
- pa_cvolume_set_balance(&v, &data->channel_map, f*2.0-1.0);
+ if (pa_channel_map_can_fade(&data->sink->channel_map)) {
+ pa_log_debug("Positioning event sound '%s' vertically at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f);
+ pa_cvolume_set_fade(&v, &data->sink->channel_map, f*2.0-1.0);
+ }
+ }
pa_log_debug("Final volume factor %s.", pa_cvolume_snprint(t, sizeof(t), &v));
-
- pa_sink_input_new_data_apply_volume_factor(data, &v);
+ pa_sink_input_new_data_apply_volume_factor_sink(data, &v);
return PA_HOOK_OK;
}
diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c
index 119f5b9f..43748bd0 100644
--- a/src/modules/module-remap-sink.c
+++ b/src/modules/module-remap-sink.c
@@ -1,7 +1,7 @@
/***
This file is part of PulseAudio.
- Copyright 2004-2008 Lennart Poettering
+ Copyright 2004-2009 Lennart Poettering
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
@@ -48,17 +48,18 @@ PA_MODULE_USAGE(
"master=<name of sink to remap> "
"master_channel_map=<channel map> "
"format=<sample format> "
- "channels=<number of channels> "
"rate=<sample rate> "
+ "channels=<number of channels> "
"channel_map=<channel map> "
"remix=<remix channels?>");
struct userdata {
- pa_core *core;
pa_module *module;
- pa_sink *sink, *master;
+ pa_sink *sink;
pa_sink_input *sink_input;
+
+ pa_bool_t auto_desc;
};
static const char* const valid_modargs[] = {
@@ -80,19 +81,24 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
switch (code) {
- case PA_SINK_MESSAGE_GET_LATENCY: {
- pa_usec_t usec = 0;
+ case PA_SINK_MESSAGE_GET_LATENCY:
+
+ /* The sink is _put() before the sink input is, so let's
+ * make sure we don't access it yet */
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
+ *((pa_usec_t*) data) = 0;
+ return 0;
+ }
- /* Get the latency of the master sink */
- if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
- usec = 0;
+ *((pa_usec_t*) data) =
+ /* Get the latency of the master sink */
+ pa_sink_get_latency_within_thread(u->sink_input->sink) +
- /* Add the latency internal to our sink input on top */
- usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec);
+ /* Add the latency internal to our sink input on top */
+ pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
- *((pa_usec_t*) data) = usec;
return 0;
- }
}
return pa_sink_process_msg(o, code, data, offset, chunk);
@@ -105,12 +111,11 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
- if (PA_SINK_IS_LINKED(state) &&
- u->sink_input &&
- PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
-
- pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
+ if (!PA_SINK_IS_LINKED(state) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return 0;
+ pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
return 0;
}
@@ -121,6 +126,10 @@ static void sink_request_rewind(pa_sink *s) {
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+ return;
+
pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes, TRUE, FALSE, FALSE);
}
@@ -131,6 +140,10 @@ static void sink_update_requested_latency(pa_sink *s) {
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+ return;
+
/* Just hand this one over to the master sink */
pa_sink_input_set_requested_latency_within_thread(
u->sink_input,
@@ -145,8 +158,8 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
pa_assert(chunk);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
- return -1;
+ /* Hmm, process any rewind request that might be queued up */
+ pa_sink_process_rewind(u->sink, 0);
pa_sink_render(u->sink, nbytes, chunk);
return 0;
@@ -160,9 +173,6 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
- return;
-
if (u->sink->thread_info.rewind_nbytes > 0) {
amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes);
u->sink->thread_info.rewind_nbytes = 0;
@@ -178,9 +188,6 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
- return;
-
pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
}
@@ -191,9 +198,6 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
- return;
-
pa_sink_set_max_request_within_thread(u->sink, nbytes);
}
@@ -204,24 +208,28 @@ static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
- return;
-
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
}
/* Called from I/O thread context */
-static void sink_input_detach_cb(pa_sink_input *i) {
+static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
- return;
+ pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+}
+
+/* Called from I/O thread context */
+static void sink_input_detach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
pa_sink_detach_within_thread(u->sink);
- pa_sink_set_asyncmsgq(u->sink, NULL);
+
pa_sink_set_rtpoll(u->sink, NULL);
}
@@ -232,14 +240,13 @@ static void sink_input_attach_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
- return;
+ pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
+ pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+ pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+ pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i));
+ pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
- pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq);
- pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);
pa_sink_attach_within_thread(u->sink);
-
- pa_sink_set_latency_range_within_thread(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency);
}
/* Called from main context */
@@ -249,14 +256,18 @@ static void sink_input_kill_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- pa_sink_unlink(u->sink);
+ /* The order here matters! We first kill the sink input, followed
+ * by the sink. That means the sink callbacks must be protected
+ * against an unconnected sink input! */
pa_sink_input_unlink(u->sink_input);
+ pa_sink_unlink(u->sink);
- pa_sink_unref(u->sink);
- u->sink = NULL;
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+
pa_module_unload_request(u->module, TRUE);
}
@@ -286,12 +297,37 @@ static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
return u->sink != dest;
}
+/* Called from main context */
+static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ if (dest) {
+ pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
+ pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
+ } else
+ pa_sink_set_asyncmsgq(u->sink, NULL);
+
+ if (u->auto_desc && dest) {
+ const char *k;
+ pa_proplist *pl;
+
+ pl = pa_proplist_new();
+ k = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : dest->name);
+
+ pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
+ pa_proplist_free(pl);
+ }
+}
+
int pa__init(pa_module*m) {
struct userdata *u;
pa_sample_spec ss;
pa_channel_map sink_map, stream_map;
pa_modargs *ma;
- const char *k;
pa_sink *master;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
@@ -336,12 +372,8 @@ int pa__init(pa_module*m) {
}
u = pa_xnew0(struct userdata, 1);
- u->core = m->core;
u->module = m;
m->userdata = u;
- u->master = master;
- u->sink = NULL;
- u->sink_input = NULL;
/* Create sink */
pa_sink_new_data_init(&sink_data);
@@ -351,8 +383,6 @@ int pa__init(pa_module*m) {
sink_data.name = pa_sprintf_malloc("%s.remapped", master->name);
pa_sink_new_data_set_sample_spec(&sink_data, &ss);
pa_sink_new_data_set_channel_map(&sink_data, &sink_map);
- k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
- pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
@@ -362,7 +392,14 @@ int pa__init(pa_module*m) {
goto fail;
}
- u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY);
+ if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
+ const char *k;
+
+ k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name);
+ }
+
+ u->sink = pa_sink_new(m->core, &sink_data, master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY));
pa_sink_new_data_done(&sink_data);
if (!u->sink) {
@@ -377,19 +414,19 @@ int pa__init(pa_module*m) {
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
- pa_sink_set_rtpoll(u->sink, master->rtpoll);
/* Create sink input */
pa_sink_input_new_data_init(&sink_input_data);
sink_input_data.driver = __FILE__;
sink_input_data.module = m;
- sink_input_data.sink = u->master;
+ sink_input_data.sink = master;
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream");
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
pa_sink_input_new_data_set_channel_map(&sink_input_data, &stream_map);
+ sink_input_data.flags = (remix ? 0 : PA_SINK_INPUT_NO_REMIX);
- pa_sink_input_new(&u->sink_input, m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE | (remix ? 0 : PA_SINK_INPUT_NO_REMIX));
+ pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
pa_sink_input_new_data_done(&sink_input_data);
if (!u->sink_input)
@@ -400,11 +437,13 @@ int pa__init(pa_module*m) {
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
u->sink_input->update_max_request = sink_input_update_max_request_cb;
u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
+ u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb;
u->sink_input->kill = sink_input_kill_cb;
u->sink_input->state_change = sink_input_state_change_cb;
u->sink_input->may_move_to = sink_input_may_move_to_cb;
+ u->sink_input->moving = sink_input_moving_cb;
u->sink_input->userdata = u;
pa_sink_put(u->sink);
@@ -440,15 +479,20 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
- if (u->sink) {
- pa_sink_unlink(u->sink);
- pa_sink_unref(u->sink);
- }
+ /* See comments in sink_input_kill_cb() above regarding
+ * destruction order! */
- if (u->sink_input) {
+ if (u->sink_input)
pa_sink_input_unlink(u->sink_input);
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->sink_input)
pa_sink_input_unref(u->sink_input);
- }
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
pa_xfree(u);
}
diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c
index c23feceb..722d84b2 100644
--- a/src/modules/module-rescue-streams.c
+++ b/src/modules/module-rescue-streams.c
@@ -45,13 +45,46 @@ static const char* const valid_modargs[] = {
};
struct userdata {
- pa_hook_slot *sink_slot, *source_slot;
+ pa_hook_slot
+ *sink_unlink_slot,
+ *source_unlink_slot,
+ *sink_input_move_fail_slot,
+ *source_output_move_fail_slot;
};
-static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) {
+static pa_sink* find_evacuation_sink(pa_core *c, pa_sink_input *i, pa_sink *skip) {
+ pa_sink *target, *def;
+ uint32_t idx;
+
+ pa_assert(c);
+ pa_assert(i);
+
+ def = pa_namereg_get_default_sink(c);
+
+ if (def && def != skip && pa_sink_input_may_move_to(i, def))
+ return def;
+
+ PA_IDXSET_FOREACH(target, c->sinks, idx) {
+ if (target == def)
+ continue;
+
+ if (target == skip)
+ continue;
+
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(target)))
+ continue;
+
+ if (pa_sink_input_may_move_to(i, target))
+ return target;
+ }
+
+ pa_log_debug("No evacuation sink found.");
+ return NULL;
+}
+
+static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) {
pa_sink_input *i;
uint32_t idx;
- pa_sink *target;
pa_assert(c);
pa_assert(sink);
@@ -65,21 +98,12 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user
return PA_HOOK_OK;
}
- if (!(target = pa_namereg_get_default_sink(c)) || target == sink) {
-
- PA_IDXSET_FOREACH(target, c->sinks, idx)
- if (target != sink)
- break;
-
- if (!target) {
- pa_log_debug("No evacuation sink found.");
- return PA_HOOK_OK;
- }
- }
+ PA_IDXSET_FOREACH(i, sink->inputs, idx) {
+ pa_sink *target;
- pa_assert(target != sink);
+ if (!(target = find_evacuation_sink(c, i, sink)))
+ continue;
- PA_IDXSET_FOREACH(i, sink->inputs, idx) {
if (pa_sink_input_move_to(i, target, FALSE) < 0)
pa_log_info("Failed to move sink input %u \"%s\" to %s.", i->index,
pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name);
@@ -91,9 +115,66 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user
return PA_HOOK_OK;
}
-static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void* userdata) {
+static pa_hook_result_t sink_input_move_fail_hook_callback(pa_core *c, pa_sink_input *i, void *userdata) {
+ pa_sink *target;
+
+ pa_assert(c);
+ pa_assert(i);
+
+ /* There's no point in doing anything if the core is shut down anyway */
+ if (c->state == PA_CORE_SHUTDOWN)
+ return PA_HOOK_OK;
+
+ if (!(target = find_evacuation_sink(c, i, NULL)))
+ return PA_HOOK_OK;
+
+ if (pa_sink_input_finish_move(i, target, FALSE) < 0) {
+ pa_log_info("Failed to move sink input %u \"%s\" to %s.", i->index,
+ pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name);
+ return PA_HOOK_OK;
+
+ } else {
+ pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index,
+ pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name);
+ return PA_HOOK_STOP;
+ }
+}
+
+static pa_source* find_evacuation_source(pa_core *c, pa_source_output *o, pa_source *skip) {
+ pa_source *target, *def;
+ uint32_t idx;
+
+ pa_assert(c);
+ pa_assert(o);
+
+ def = pa_namereg_get_default_source(c);
+
+ if (def && def != skip && pa_source_output_may_move_to(o, def))
+ return def;
+
+ PA_IDXSET_FOREACH(target, c->sources, idx) {
+ if (target == def)
+ continue;
+
+ if (target == skip)
+ continue;
+
+ if (!target->monitor_of != !skip->monitor_of)
+ continue;
+
+ if (!PA_SOURCE_IS_LINKED(pa_source_get_state(target)))
+ continue;
+
+ if (pa_source_output_may_move_to(o, target))
+ return target;
+ }
+
+ pa_log_debug("No evacuation source found.");
+ return NULL;
+}
+
+static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, void* userdata) {
pa_source_output *o;
- pa_source *target;
uint32_t idx;
pa_assert(c);
@@ -108,21 +189,12 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void
return PA_HOOK_OK;
}
- if (!(target = pa_namereg_get_default_source(c)) || target == source) {
-
- PA_IDXSET_FOREACH(target, c->sources, idx)
- if (target != source && !target->monitor_of == !source->monitor_of)
- break;
-
- if (!target) {
- pa_log_info("No evacuation source found.");
- return PA_HOOK_OK;
- }
- }
+ PA_IDXSET_FOREACH(o, source->outputs, idx) {
+ pa_source *target;
- pa_assert(target != source);
+ if (!(target = find_evacuation_source(c, o, source)))
+ continue;
- PA_IDXSET_FOREACH(o, source->outputs, idx) {
if (pa_source_output_move_to(o, target, FALSE) < 0)
pa_log_info("Failed to move source output %u \"%s\" to %s.", o->index,
pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), target->name);
@@ -134,6 +206,31 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void
return PA_HOOK_OK;
}
+static pa_hook_result_t source_output_move_fail_hook_callback(pa_core *c, pa_source_output *i, void *userdata) {
+ pa_source *target;
+
+ pa_assert(c);
+ pa_assert(i);
+
+ /* There's no point in doing anything if the core is shut down anyway */
+ if (c->state == PA_CORE_SHUTDOWN)
+ return PA_HOOK_OK;
+
+ if (!(target = find_evacuation_source(c, i, NULL)))
+ return PA_HOOK_OK;
+
+ if (pa_source_output_finish_move(i, target, FALSE) < 0) {
+ pa_log_info("Failed to move source input %u \"%s\" to %s.", i->index,
+ pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name);
+ return PA_HOOK_OK;
+
+ } else {
+ pa_log_info("Sucessfully moved source input %u \"%s\" to %s.", i->index,
+ pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name);
+ return PA_HOOK_STOP;
+ }
+}
+
int pa__init(pa_module*m) {
pa_modargs *ma;
struct userdata *u;
@@ -148,8 +245,11 @@ int pa__init(pa_module*m) {
m->userdata = u = pa_xnew(struct userdata, 1);
/* A little bit later than module-stream-restore, module-intended-roles... */
- u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) sink_hook_callback, u);
- u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) source_hook_callback, u);
+ u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) sink_unlink_hook_callback, u);
+ u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) source_unlink_hook_callback, u);
+
+ u->sink_input_move_fail_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], PA_HOOK_LATE+20, (pa_hook_cb_t) sink_input_move_fail_hook_callback, u);
+ u->source_output_move_fail_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], PA_HOOK_LATE+20, (pa_hook_cb_t) source_output_move_fail_hook_callback, u);
pa_modargs_free(ma);
return 0;
@@ -163,10 +263,15 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
- if (u->sink_slot)
- pa_hook_slot_free(u->sink_slot);
- if (u->source_slot)
- pa_hook_slot_free(u->source_slot);
+ if (u->sink_unlink_slot)
+ pa_hook_slot_free(u->sink_unlink_slot);
+ if (u->source_unlink_slot)
+ pa_hook_slot_free(u->source_unlink_slot);
+
+ if (u->sink_input_move_fail_slot)
+ pa_hook_slot_free(u->sink_input_move_fail_slot);
+ if (u->source_output_move_fail_slot)
+ pa_hook_slot_free(u->source_output_move_fail_slot);
pa_xfree(u);
}
diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c
index 0be1d722..69b20028 100644
--- a/src/modules/module-sine.c
+++ b/src/modules/module-sine.c
@@ -163,7 +163,7 @@ int pa__init(pa_module*m) {
pa_proplist_setf(data.proplist, "sine.hz", "%u", frequency);
pa_sink_input_new_data_set_sample_spec(&data, &ss);
- pa_sink_input_new(&u->sink_input, m->core, &data, 0);
+ pa_sink_input_new(&u->sink_input, m->core, &data);
pa_sink_input_new_data_done(&data);
if (!u->sink_input)
diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c
index 0920d25e..955997ba 100644
--- a/src/modules/module-solaris.c
+++ b/src/modules/module-solaris.c
@@ -60,6 +60,7 @@
#include <pulsecore/thread-mq.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/thread.h>
+#include <pulsecore/time-smoother.h>
#include "module-solaris-symdef.h"
@@ -110,6 +111,8 @@ struct userdata {
uint32_t prev_playback_samples, prev_record_samples;
int32_t minimum_request;
+
+ pa_smoother *smoother;
};
static const char* const valid_modargs[] = {
@@ -133,6 +136,9 @@ static const char* const valid_modargs[] = {
#define MAX_RENDER_HZ (300)
/* This render rate limit imposes a minimum latency, but without it we waste too much CPU time. */
+#define MAX_BUFFER_SIZE (128 * 1024)
+/* An attempt to buffer more than 128 KB causes write() to fail with errno == EAGAIN. */
+
static uint64_t get_playback_buffered_bytes(struct userdata *u) {
audio_info_t info;
uint64_t played_bytes;
@@ -145,7 +151,12 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) {
/* Handle wrap-around of the device's sample counter, which is a uint_32. */
if (u->prev_playback_samples > info.play.samples) {
- /* Unfortunately info.play.samples can sometimes go backwards, even before it wraps! */
+ /*
+ * Unfortunately info.play.samples can sometimes go backwards, even before it wraps!
+ * The bug seems to be absent on Solaris x86 nv117 with audio810 driver, at least on this (UP) machine.
+ * The bug is present on a different (SMP) machine running Solaris x86 nv103 with audioens driver.
+ * An earlier revision of this file mentions the same bug independently (unknown configuration).
+ */
if (u->prev_playback_samples + info.play.samples < 240000) {
++u->play_samples_msw;
} else {
@@ -155,6 +166,8 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) {
u->prev_playback_samples = info.play.samples;
played_bytes = (((uint64_t)u->play_samples_msw << 32) + info.play.samples) * u->frame_size;
+ pa_smoother_put(u->smoother, pa_rtclock_now(), pa_bytes_to_usec(played_bytes, &u->sink->sample_spec));
+
return u->written_bytes - played_bytes;
}
@@ -314,7 +327,7 @@ static int open_audio_device(struct userdata *u, pa_sample_spec *ss) {
pa_assert(u);
pa_assert(ss);
- if ((u->fd = open(u->device_name, u->mode | O_NONBLOCK)) < 0) {
+ if ((u->fd = pa_open_cloexec(u->device_name, u->mode | O_NONBLOCK)) < 0) {
pa_log_warn("open %s failed (%s)", u->device_name, pa_cstrerror(errno));
return -1;
}
@@ -387,6 +400,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
+ pa_smoother_pause(u->smoother, pa_rtclock_now());
+
if (!u->source || u->source_suspended) {
if (suspend(u) < 0)
return -1;
@@ -398,6 +413,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
case PA_SINK_RUNNING:
if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
+ pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE);
+
if (!u->source || u->source_suspended) {
if (unsuspend(u) < 0)
return -1;
@@ -479,7 +496,7 @@ static void sink_set_volume(pa_sink *s) {
if (u->fd >= 0) {
AUDIO_INITINFO(&info);
- info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
+ info.play.gain = pa_cvolume_max(&s->real_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
assert(info.play.gain <= AUDIO_MAX_GAIN);
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
@@ -501,8 +518,7 @@ static void sink_get_volume(pa_sink *s) {
if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0)
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
else
- pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels,
- info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
+ pa_cvolume_set(&s->real_volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
}
}
@@ -515,7 +531,7 @@ static void source_set_volume(pa_source *s) {
if (u->fd >= 0) {
AUDIO_INITINFO(&info);
- info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
+ info.play.gain = pa_cvolume_max(&s->volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
assert(info.play.gain <= AUDIO_MAX_GAIN);
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
@@ -537,8 +553,7 @@ static void source_get_volume(pa_source *s) {
if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0)
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
else
- pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels,
- info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
+ pa_cvolume_set(&s->volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
}
}
@@ -585,6 +600,10 @@ static void process_rewind(struct userdata *u) {
pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes);
rewind_nbytes = PA_MIN(u->memchunk.length, rewind_nbytes);
u->memchunk.length -= rewind_nbytes;
+ if (u->memchunk.length <= 0 && u->memchunk.memblock) {
+ pa_memblock_unref(u->memchunk.memblock);
+ pa_memchunk_reset(&u->memchunk);
+ }
pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes);
}
@@ -606,11 +625,13 @@ static void thread_func(void *userdata) {
pa_thread_mq_install(&u->thread_mq);
+ pa_smoother_set_time_offset(u->smoother, pa_rtclock_now());
+
for (;;) {
/* Render some data and write it to the dsp */
if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
- pa_usec_t xtime0;
+ pa_usec_t xtime0, ysleep_interval, xsleep_interval;
uint64_t buffered_bytes;
if (u->sink->thread_info.rewind_requested)
@@ -629,12 +650,15 @@ static void thread_func(void *userdata) {
info.play.error = 0;
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0)
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
+
+ pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE);
}
for (;;) {
void *p;
ssize_t w;
size_t len;
+ int write_type = 1;
/*
* Since we cannot modify the size of the output buffer we fake it
@@ -651,39 +675,32 @@ static void thread_func(void *userdata) {
if (len < (size_t) u->minimum_request)
break;
- if (u->memchunk.length < len)
+ if (!u->memchunk.length)
pa_sink_render(u->sink, u->sink->thread_info.max_request, &u->memchunk);
+ len = PA_MIN(u->memchunk.length, len);
+
p = pa_memblock_acquire(u->memchunk.memblock);
- w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, NULL);
+ w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, len, &write_type);
pa_memblock_release(u->memchunk.memblock);
if (w <= 0) {
- switch (errno) {
- case EINTR:
- continue;
- case EAGAIN:
- /* If the buffer_size is too big, we get EAGAIN. Avoiding that limit by trial and error
- * is not ideal, but I don't know how to get the system to tell me what the limit is.
- */
- u->buffer_size = u->buffer_size * 18 / 25;
- u->buffer_size -= u->buffer_size % u->frame_size;
- u->buffer_size = PA_MAX(u->buffer_size, 2 * u->minimum_request);
- pa_sink_set_max_request_within_thread(u->sink, u->buffer_size);
- pa_sink_set_max_rewind_within_thread(u->sink, u->buffer_size);
- pa_log("EAGAIN. Buffer size is now %u bytes (%llu buffered)", u->buffer_size, buffered_bytes);
- break;
- default:
- pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno));
- goto fail;
+ if (errno == EINTR) {
+ continue;
+ } else if (errno == EAGAIN) {
+ /* We may have realtime priority so yield the CPU to ensure that fd can become writable again. */
+ pa_log_debug("EAGAIN with %llu bytes buffered.", buffered_bytes);
+ break;
+ } else {
+ pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno));
+ goto fail;
}
} else {
pa_assert(w % u->frame_size == 0);
u->written_bytes += w;
- u->memchunk.length -= w;
-
u->memchunk.index += w;
+ u->memchunk.length -= w;
if (u->memchunk.length <= 0) {
pa_memblock_unref(u->memchunk.memblock);
pa_memchunk_reset(&u->memchunk);
@@ -691,7 +708,9 @@ static void thread_func(void *userdata) {
}
}
- pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec));
+ ysleep_interval = pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec);
+ xsleep_interval = pa_smoother_translate(u->smoother, xtime0, ysleep_interval);
+ pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + PA_MIN(xsleep_interval, ysleep_interval));
} else
pa_rtpoll_set_timer_disabled(u->rtpoll);
@@ -797,7 +816,7 @@ static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void
pa_log_debug("caught signal");
if (u->sink) {
- pa_sink_get_volume(u->sink, TRUE, FALSE);
+ pa_sink_get_volume(u->sink, TRUE);
pa_sink_get_mute(u->sink, TRUE);
}
@@ -812,7 +831,7 @@ int pa__init(pa_module *m) {
pa_channel_map map;
pa_modargs *ma = NULL;
uint32_t buffer_length_msec;
- int fd;
+ int fd = -1;
pa_sink_new_data sink_new_data;
pa_source_new_data source_new_data;
char const *name;
@@ -838,6 +857,9 @@ int pa__init(pa_module *m) {
u = pa_xnew0(struct userdata, 1);
+ if (!(u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC * 2, TRUE, TRUE, 10, pa_rtclock_now(), TRUE)))
+ goto fail;
+
/*
* For a process (or several processes) to use the same audio device for both
* record and playback at the same time, the device's mixer must be enabled.
@@ -861,7 +883,13 @@ int pa__init(pa_module *m) {
}
u->buffer_size = pa_usec_to_bytes(1000 * buffer_length_msec, &ss);
if (u->buffer_size < 2 * u->minimum_request) {
- pa_log("supplied buffer size argument is too small");
+ pa_log("buffer_length argument cannot be smaller than %u",
+ (unsigned)(pa_bytes_to_usec(2 * u->minimum_request, &ss) / 1000));
+ goto fail;
+ }
+ if (u->buffer_size > MAX_BUFFER_SIZE) {
+ pa_log("buffer_length argument cannot be greater than %u",
+ (unsigned)(pa_bytes_to_usec(MAX_BUFFER_SIZE, &ss) / 1000));
goto fail;
}
@@ -924,6 +952,7 @@ int pa__init(pa_module *m) {
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
pa_source_set_rtpoll(u->source, u->rtpoll);
+ pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->buffer_size, &u->source->sample_spec));
u->source->get_volume = source_get_volume;
u->source->set_volume = source_set_volume;
@@ -966,15 +995,15 @@ int pa__init(pa_module *m) {
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
pa_sink_set_rtpoll(u->sink, u->rtpoll);
+ pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->buffer_size, &u->sink->sample_spec));
+ pa_sink_set_max_request(u->sink, u->buffer_size);
+ pa_sink_set_max_rewind(u->sink, u->buffer_size);
u->sink->get_volume = sink_get_volume;
u->sink->set_volume = sink_set_volume;
u->sink->get_mute = sink_get_mute;
u->sink->set_mute = sink_set_mute;
u->sink->refresh_volume = u->sink->refresh_muted = TRUE;
-
- pa_sink_set_max_request(u->sink, u->buffer_size);
- pa_sink_set_max_rewind(u->sink, u->buffer_size);
} else
u->sink = NULL;
@@ -1075,6 +1104,9 @@ void pa__done(pa_module *m) {
if (u->fd >= 0)
close(u->fd);
+ if (u->smoother)
+ pa_smoother_free(u->smoother);
+
pa_xfree(u->device_name);
pa_xfree(u);
diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c
index 8c0bb6b0..788f458b 100644
--- a/src/modules/module-stream-restore.c
+++ b/src/modules/module-stream-restore.c
@@ -2,6 +2,7 @@
This file is part of PulseAudio.
Copyright 2008 Lennart Poettering
+ Copyright 2009 Tanu Kaskinen
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
@@ -51,6 +52,11 @@
#include <pulsecore/pstream-util.h>
#include <pulsecore/database.h>
+#ifdef HAVE_DBUS
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+#endif
+
#include "module-stream-restore-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering");
@@ -100,17 +106,24 @@ struct userdata {
pa_native_protocol *protocol;
pa_idxset *subscribed;
+
+#ifdef HAVE_DBUS
+ pa_dbus_protocol *dbus_protocol;
+ pa_hashmap *dbus_entries;
+ uint32_t next_index; /* For generating object paths for entries. */
+#endif
};
-#define ENTRY_VERSION 2
+#define ENTRY_VERSION 3
struct entry {
uint8_t version;
- pa_bool_t muted_valid:1, volume_valid:1, device_valid:1;
+ pa_bool_t muted_valid:1, volume_valid:1, device_valid:1, card_valid:1;
pa_bool_t muted:1;
pa_channel_map channel_map;
pa_cvolume volume;
char device[PA_NAME_MAX];
+ char card[PA_NAME_MAX];
} PA_GCC_PACKED;
enum {
@@ -122,6 +135,835 @@ enum {
SUBCOMMAND_EVENT
};
+static struct entry *read_entry(struct userdata *u, const char *name);
+static void apply_entry(struct userdata *u, const char *name, struct entry *e);
+static void trigger_save(struct userdata *u);
+
+#ifdef HAVE_DBUS
+
+#define OBJECT_PATH "/org/pulseaudio/stream_restore1"
+#define ENTRY_OBJECT_NAME "entry"
+#define INTERFACE_STREAM_RESTORE "org.PulseAudio.Ext.StreamRestore1"
+#define INTERFACE_ENTRY INTERFACE_STREAM_RESTORE ".RestoreEntry"
+
+#define DBUS_INTERFACE_REVISION 0
+
+struct dbus_entry {
+ struct userdata *userdata;
+
+ char *entry_name;
+ uint32_t index;
+ char *object_path;
+};
+
+static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_entries(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_entry_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_entry_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_entry_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_entry_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_entry_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+
+static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum property_handler_index {
+ PROPERTY_HANDLER_INTERFACE_REVISION,
+ PROPERTY_HANDLER_ENTRIES,
+ PROPERTY_HANDLER_MAX
+};
+
+enum entry_property_handler_index {
+ ENTRY_PROPERTY_HANDLER_INDEX,
+ ENTRY_PROPERTY_HANDLER_NAME,
+ ENTRY_PROPERTY_HANDLER_DEVICE,
+ ENTRY_PROPERTY_HANDLER_VOLUME,
+ ENTRY_PROPERTY_HANDLER_MUTE,
+ ENTRY_PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+ [PROPERTY_HANDLER_INTERFACE_REVISION] = { .property_name = "InterfaceRevision", .type = "u", .get_cb = handle_get_interface_revision, .set_cb = NULL },
+ [PROPERTY_HANDLER_ENTRIES] = { .property_name = "Entries", .type = "ao", .get_cb = handle_get_entries, .set_cb = NULL }
+};
+
+static pa_dbus_property_handler entry_property_handlers[ENTRY_PROPERTY_HANDLER_MAX] = {
+ [ENTRY_PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_entry_get_index, .set_cb = NULL },
+ [ENTRY_PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_entry_get_name, .set_cb = NULL },
+ [ENTRY_PROPERTY_HANDLER_DEVICE] = { .property_name = "Device", .type = "s", .get_cb = handle_entry_get_device, .set_cb = handle_entry_set_device },
+ [ENTRY_PROPERTY_HANDLER_VOLUME] = { .property_name = "Volume", .type = "a(uu)", .get_cb = handle_entry_get_volume, .set_cb = handle_entry_set_volume },
+ [ENTRY_PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_entry_get_mute, .set_cb = handle_entry_set_mute }
+};
+
+enum method_handler_index {
+ METHOD_HANDLER_ADD_ENTRY,
+ METHOD_HANDLER_GET_ENTRY_BY_NAME,
+ METHOD_HANDLER_MAX
+};
+
+enum entry_method_handler_index {
+ ENTRY_METHOD_HANDLER_REMOVE,
+ ENTRY_METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info add_entry_args[] = { { "name", "s", "in" },
+ { "device", "s", "in" },
+ { "volume", "a(uu)", "in" },
+ { "mute", "b", "in" },
+ { "entry", "o", "out" } };
+static pa_dbus_arg_info get_entry_by_name_args[] = { { "name", "s", "in" }, { "entry", "o", "out" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+ [METHOD_HANDLER_ADD_ENTRY] = {
+ .method_name = "AddEntry",
+ .arguments = add_entry_args,
+ .n_arguments = sizeof(add_entry_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_add_entry },
+ [METHOD_HANDLER_GET_ENTRY_BY_NAME] = {
+ .method_name = "GetEntryByName",
+ .arguments = get_entry_by_name_args,
+ .n_arguments = sizeof(get_entry_by_name_args) / sizeof(pa_dbus_arg_info),
+ .receive_cb = handle_get_entry_by_name }
+};
+
+static pa_dbus_method_handler entry_method_handlers[ENTRY_METHOD_HANDLER_MAX] = {
+ [ENTRY_METHOD_HANDLER_REMOVE] = {
+ .method_name = "Remove",
+ .arguments = NULL,
+ .n_arguments = 0,
+ .receive_cb = handle_entry_remove }
+};
+
+enum signal_index {
+ SIGNAL_NEW_ENTRY,
+ SIGNAL_ENTRY_REMOVED,
+ SIGNAL_MAX
+};
+
+enum entry_signal_index {
+ ENTRY_SIGNAL_DEVICE_UPDATED,
+ ENTRY_SIGNAL_VOLUME_UPDATED,
+ ENTRY_SIGNAL_MUTE_UPDATED,
+ ENTRY_SIGNAL_MAX
+};
+
+static pa_dbus_arg_info new_entry_args[] = { { "entry", "o", NULL } };
+static pa_dbus_arg_info entry_removed_args[] = { { "entry", "o", NULL } };
+
+static pa_dbus_arg_info entry_device_updated_args[] = { { "device", "s", NULL } };
+static pa_dbus_arg_info entry_volume_updated_args[] = { { "volume", "a(uu)", NULL } };
+static pa_dbus_arg_info entry_mute_updated_args[] = { { "muted", "b", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+ [SIGNAL_NEW_ENTRY] = { .name = "NewEntry", .arguments = new_entry_args, .n_arguments = 1 },
+ [SIGNAL_ENTRY_REMOVED] = { .name = "EntryRemoved", .arguments = entry_removed_args, .n_arguments = 1 }
+};
+
+static pa_dbus_signal_info entry_signals[ENTRY_SIGNAL_MAX] = {
+ [ENTRY_SIGNAL_DEVICE_UPDATED] = { .name = "DeviceUpdated", .arguments = entry_device_updated_args, .n_arguments = 1 },
+ [ENTRY_SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = entry_volume_updated_args, .n_arguments = 1 },
+ [ENTRY_SIGNAL_MUTE_UPDATED] = { .name = "MuteUpdated", .arguments = entry_mute_updated_args, .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info stream_restore_interface_info = {
+ .name = INTERFACE_STREAM_RESTORE,
+ .method_handlers = method_handlers,
+ .n_method_handlers = METHOD_HANDLER_MAX,
+ .property_handlers = property_handlers,
+ .n_property_handlers = PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_get_all,
+ .signals = signals,
+ .n_signals = SIGNAL_MAX
+};
+
+static pa_dbus_interface_info entry_interface_info = {
+ .name = INTERFACE_ENTRY,
+ .method_handlers = entry_method_handlers,
+ .n_method_handlers = ENTRY_METHOD_HANDLER_MAX,
+ .property_handlers = entry_property_handlers,
+ .n_property_handlers = ENTRY_PROPERTY_HANDLER_MAX,
+ .get_all_properties_cb = handle_entry_get_all,
+ .signals = entry_signals,
+ .n_signals = ENTRY_SIGNAL_MAX
+};
+
+static struct dbus_entry *dbus_entry_new(struct userdata *u, const char *entry_name) {
+ struct dbus_entry *de;
+
+ pa_assert(u);
+ pa_assert(entry_name);
+ pa_assert(*entry_name);
+
+ de = pa_xnew(struct dbus_entry, 1);
+ de->userdata = u;
+ de->entry_name = pa_xstrdup(entry_name);
+ de->index = u->next_index++;
+ de->object_path = pa_sprintf_malloc("%s/%s%u", OBJECT_PATH, ENTRY_OBJECT_NAME, de->index);
+
+ pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, de->object_path, &entry_interface_info, u) >= 0);
+
+ return de;
+}
+
+static void dbus_entry_free(struct dbus_entry *de) {
+ pa_assert(de);
+
+ pa_assert_se(pa_dbus_protocol_remove_interface(de->userdata->dbus_protocol, de->object_path, entry_interface_info.name) >= 0);
+
+ pa_xfree(de->entry_name);
+ pa_xfree(de->object_path);
+}
+
+/* Reads an array [(UInt32, UInt32)] from the iterator. The struct items are
+ * are a channel position and a volume value, respectively. The result is
+ * stored in the map and vol arguments. The iterator must point to a "a(uu)"
+ * element. If the data is invalid, an error reply is sent and a negative
+ * number is returned. In case of a failure we make no guarantees about the
+ * state of map and vol. In case of an empty array the channels field of both
+ * map and vol are set to 0. This function calls dbus_message_iter_next(iter)
+ * before returning. */
+static int get_volume_arg(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, pa_channel_map *map, pa_cvolume *vol) {
+ DBusMessageIter array_iter;
+ DBusMessageIter struct_iter;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(pa_streq(dbus_message_iter_get_signature(iter), "a(uu)"));
+ pa_assert(map);
+ pa_assert(vol);
+
+ pa_channel_map_init(map);
+ pa_cvolume_init(vol);
+
+ map->channels = 0;
+ vol->channels = 0;
+
+ dbus_message_iter_recurse(iter, &array_iter);
+
+ while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID) {
+ dbus_uint32_t chan_pos;
+ dbus_uint32_t chan_vol;
+
+ dbus_message_iter_recurse(&array_iter, &struct_iter);
+
+ dbus_message_iter_get_basic(&struct_iter, &chan_pos);
+
+ if (chan_pos >= PA_CHANNEL_POSITION_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position: %u", chan_pos);
+ return -1;
+ }
+
+ pa_assert_se(dbus_message_iter_next(&struct_iter));
+ dbus_message_iter_get_basic(&struct_iter, &chan_vol);
+
+ if (chan_vol > PA_VOLUME_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u", chan_vol);
+ return -1;
+ }
+
+ if (map->channels < PA_CHANNELS_MAX) {
+ map->map[map->channels] = chan_pos;
+ vol->values[map->channels] = chan_vol;
+ }
+ ++map->channels;
+ ++vol->channels;
+
+ dbus_message_iter_next(&array_iter);
+ }
+
+ if (map->channels > PA_CHANNELS_MAX) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too many channels: %u. The maximum is %u.", map->channels, PA_CHANNELS_MAX);
+ return -1;
+ }
+
+ dbus_message_iter_next(iter);
+
+ return 0;
+}
+
+static void append_volume(DBusMessageIter *iter, struct entry *e) {
+ DBusMessageIter array_iter;
+ DBusMessageIter struct_iter;
+ unsigned i;
+
+ pa_assert(iter);
+ pa_assert(e);
+
+ pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "(uu)", &array_iter));
+
+ if (!e->volume_valid) {
+ pa_assert_se(dbus_message_iter_close_container(iter, &array_iter));
+ return;
+ }
+
+ for (i = 0; i < e->channel_map.channels; ++i) {
+ pa_assert_se(dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter));
+
+ pa_assert_se(dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &e->channel_map.map[i]));
+ pa_assert_se(dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &e->volume.values[i]));
+
+ pa_assert_se(dbus_message_iter_close_container(&array_iter, &struct_iter));
+ }
+
+ pa_assert_se(dbus_message_iter_close_container(iter, &array_iter));
+}
+
+static void append_volume_variant(DBusMessageIter *iter, struct entry *e) {
+ DBusMessageIter variant_iter;
+
+ pa_assert(iter);
+ pa_assert(e);
+
+ pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a(uu)", &variant_iter));
+
+ append_volume(&variant_iter, e);
+
+ pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter));
+}
+
+static void send_new_entry_signal(struct dbus_entry *entry) {
+ DBusMessage *signal;
+
+ pa_assert(entry);
+
+ pa_assert_se(signal = dbus_message_new_signal(OBJECT_PATH, INTERFACE_STREAM_RESTORE, signals[SIGNAL_NEW_ENTRY].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &entry->object_path, DBUS_TYPE_INVALID));
+ pa_dbus_protocol_send_signal(entry->userdata->dbus_protocol, signal);
+ dbus_message_unref(signal);
+}
+
+static void send_entry_removed_signal(struct dbus_entry *entry) {
+ DBusMessage *signal;
+
+ pa_assert(entry);
+
+ pa_assert_se(signal = dbus_message_new_signal(OBJECT_PATH, INTERFACE_STREAM_RESTORE, signals[SIGNAL_ENTRY_REMOVED].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &entry->object_path, DBUS_TYPE_INVALID));
+ pa_dbus_protocol_send_signal(entry->userdata->dbus_protocol, signal);
+ dbus_message_unref(signal);
+}
+
+static void send_device_updated_signal(struct dbus_entry *de, struct entry *e) {
+ DBusMessage *signal;
+ const char *device;
+
+ pa_assert(de);
+ pa_assert(e);
+
+ device = e->device_valid ? e->device : "";
+
+ pa_assert_se(signal = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_DEVICE_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_STRING, &device, DBUS_TYPE_INVALID));
+ pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal);
+ dbus_message_unref(signal);
+}
+
+static void send_volume_updated_signal(struct dbus_entry *de, struct entry *e) {
+ DBusMessage *signal;
+ DBusMessageIter msg_iter;
+
+ pa_assert(de);
+ pa_assert(e);
+
+ pa_assert_se(signal = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_VOLUME_UPDATED].name));
+ dbus_message_iter_init_append(signal, &msg_iter);
+ append_volume(&msg_iter, e);
+ pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal);
+ dbus_message_unref(signal);
+}
+
+static void send_mute_updated_signal(struct dbus_entry *de, struct entry *e) {
+ DBusMessage *signal;
+ dbus_bool_t muted;
+
+ pa_assert(de);
+ pa_assert(e);
+
+ pa_assert(e->muted_valid);
+
+ muted = e->muted;
+
+ pa_assert_se(signal = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_MUTE_UPDATED].name));
+ pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_BOOLEAN, &muted, DBUS_TYPE_INVALID));
+ pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal);
+ dbus_message_unref(signal);
+}
+
+static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ dbus_uint32_t interface_revision = DBUS_INTERFACE_REVISION;
+
+ pa_assert(conn);
+ pa_assert(msg);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &interface_revision);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_entries(struct userdata *u, unsigned *n) {
+ const char **entries;
+ unsigned i = 0;
+ void *state = NULL;
+ struct dbus_entry *de;
+
+ pa_assert(u);
+ pa_assert(n);
+
+ *n = pa_hashmap_size(u->dbus_entries);
+
+ if (*n == 0)
+ return NULL;
+
+ entries = pa_xnew(const char *, *n);
+
+ PA_HASHMAP_FOREACH(de, u->dbus_entries, state)
+ entries[i++] = de->object_path;
+
+ return entries;
+}
+
+static void handle_get_entries(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct userdata *u = userdata;
+ const char **entries;
+ unsigned n;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(u);
+
+ entries = get_entries(u, &n);
+
+ pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, entries, n);
+
+ pa_xfree(entries);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct userdata *u = userdata;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ dbus_uint32_t interface_revision;
+ const char **entries;
+ unsigned n_entries;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(u);
+
+ interface_revision = DBUS_INTERFACE_REVISION;
+ entries = get_entries(u, &n_entries);
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INTERFACE_REVISION].property_name, DBUS_TYPE_UINT32, &interface_revision);
+ pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ENTRIES].property_name, DBUS_TYPE_OBJECT_PATH, entries, n_entries);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+
+ pa_xfree(entries);
+}
+
+static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct userdata *u = userdata;
+ DBusMessageIter msg_iter;
+ const char *name = NULL;
+ const char *device = NULL;
+ pa_channel_map map;
+ pa_cvolume vol;
+ dbus_bool_t muted = FALSE;
+ dbus_bool_t apply_immediately = FALSE;
+ pa_datum key;
+ pa_datum value;
+ struct dbus_entry *dbus_entry = NULL;
+ struct entry *e = NULL;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(u);
+
+ pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+ dbus_message_iter_get_basic(&msg_iter, &name);
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ dbus_message_iter_get_basic(&msg_iter, &device);
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ if (get_volume_arg(conn, msg, &msg_iter, &map, &vol) < 0)
+ return;
+
+ dbus_message_iter_get_basic(&msg_iter, &muted);
+
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ dbus_message_iter_get_basic(&msg_iter, &apply_immediately);
+
+ if (!*name) {
+ pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "An empty string was given as the entry name.");
+ return;
+ }
+
+ if ((dbus_entry = pa_hashmap_get(u->dbus_entries, name))) {
+ pa_bool_t mute_updated = FALSE;
+ pa_bool_t volume_updated = FALSE;
+ pa_bool_t device_updated = FALSE;
+
+ pa_assert_se(e = read_entry(u, name));
+ mute_updated = e->muted != muted;
+ e->muted = muted;
+ e->muted_valid = TRUE;
+
+ volume_updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol);
+ e->volume = vol;
+ e->channel_map = map;
+ e->volume_valid = !!map.channels;
+
+ device_updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device);
+ pa_strlcpy(e->device, device, sizeof(e->device));
+ e->device_valid = !!device[0];
+
+ if (mute_updated)
+ send_mute_updated_signal(dbus_entry, e);
+ if (volume_updated)
+ send_volume_updated_signal(dbus_entry, e);
+ if (device_updated)
+ send_device_updated_signal(dbus_entry, e);
+
+ } else {
+ dbus_entry = dbus_entry_new(u, name);
+ pa_assert(pa_hashmap_put(u->dbus_entries, dbus_entry->entry_name, dbus_entry) >= 0);
+
+ e->muted_valid = TRUE;
+ e->volume_valid = !!map.channels;
+ e->device_valid = !!device[0];
+ e->muted = muted;
+ e->volume = vol;
+ e->channel_map = map;
+ pa_strlcpy(e->device, device, sizeof(e->device));
+
+ send_new_entry_signal(dbus_entry);
+ }
+
+ key.data = (char *) name;
+ key.size = strlen(name);
+
+ value.data = e;
+ value.size = sizeof(struct entry);
+
+ pa_assert_se(pa_database_set(u->database, &key, &value, TRUE) == 0);
+ if (apply_immediately)
+ apply_entry(u, name, e);
+
+ trigger_save(u);
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+ pa_xfree(e);
+}
+
+static void handle_get_entry_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct userdata *u = userdata;
+ const char *name;
+ struct dbus_entry *de;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(u);
+
+ pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID));
+
+ if (!(de = pa_hashmap_get(u->dbus_entries, name))) {
+ pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such stream restore entry.");
+ return;
+ }
+
+ pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &de->object_path);
+}
+
+static void handle_entry_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &de->index);
+}
+
+static void handle_entry_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &de->entry_name);
+}
+
+static void handle_entry_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+ struct entry *e;
+ const char *device;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+ device = e->device_valid ? e->device : "";
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &device);
+
+ pa_xfree(e);
+}
+
+static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ struct dbus_entry *de = userdata;
+ const char *device;
+ struct entry *e;
+ pa_bool_t updated;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(de);
+
+ dbus_message_iter_get_basic(iter, &device);
+
+ pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+ updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device);
+
+ if (updated) {
+ pa_datum key;
+ pa_datum value;
+
+ pa_strlcpy(e->device, device, sizeof(e->device));
+ e->device_valid = !!device[0];
+
+ key.data = de->entry_name;
+ key.size = strlen(de->entry_name);
+ value.data = e;
+ value.size = sizeof(struct entry);
+ pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0);
+
+ send_device_updated_signal(de, e);
+ trigger_save(de->userdata);
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+ pa_xfree(e);
+}
+
+static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+ DBusMessage *reply;
+ DBusMessageIter msg_iter;
+ struct entry *e;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+ pa_assert_se(reply = dbus_message_new_method_return(msg));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ append_volume_variant(&msg_iter, e);
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ pa_xfree(e);
+}
+
+static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ struct dbus_entry *de = userdata;
+ pa_channel_map map;
+ pa_cvolume vol;
+ struct entry *e = NULL;
+ pa_bool_t updated = FALSE;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(de);
+
+ if (get_volume_arg(conn, msg, iter, &map, &vol) < 0)
+ return;
+
+ pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+ updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol);
+
+ if (updated) {
+ pa_datum key;
+ pa_datum value;
+
+ e->volume = vol;
+ e->channel_map = map;
+ e->volume_valid = !!map.channels;
+
+ key.data = de->entry_name;
+ key.size = strlen(de->entry_name);
+ value.data = e;
+ value.size = sizeof(struct entry);
+ pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0);
+
+ send_volume_updated_signal(de, e);
+ trigger_save(de->userdata);
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+ pa_xfree(e);
+}
+
+static void handle_entry_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+ struct entry *e;
+ dbus_bool_t mute;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+ mute = e->muted_valid ? e->muted : FALSE;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &mute);
+
+ pa_xfree(e);
+}
+
+static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ struct dbus_entry *de = userdata;
+ dbus_bool_t mute;
+ struct entry *e;
+ pa_bool_t updated;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(de);
+
+ dbus_message_iter_get_basic(iter, &mute);
+
+ pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+ updated = !e->muted_valid || e->muted != mute;
+
+ if (updated) {
+ pa_datum key;
+ pa_datum value;
+
+ e->muted = mute;
+ e->muted_valid = TRUE;
+
+ key.data = de->entry_name;
+ key.size = strlen(de->entry_name);
+ value.data = e;
+ value.size = sizeof(struct entry);
+ pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0);
+
+ send_mute_updated_signal(de, e);
+ trigger_save(de->userdata);
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+ pa_xfree(e);
+}
+
+static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+ struct entry *e;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ DBusMessageIter dict_entry_iter;
+ const char *device;
+ dbus_bool_t mute;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+ device = e->device_valid ? e->device : "";
+ mute = e->muted_valid ? e->muted : FALSE;
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &de->index);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &de->entry_name);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_DEVICE].property_name, DBUS_TYPE_STRING, &device);
+
+ pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+
+ pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &entry_property_handlers[ENTRY_PROPERTY_HANDLER_VOLUME].property_name));
+ append_volume_variant(&dict_entry_iter, e);
+
+ pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &mute);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+
+ pa_xfree(e);
+}
+
+static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+ pa_datum key;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ key.data = de->entry_name;
+ key.size = strlen(de->entry_name);
+
+ pa_assert_se(pa_database_unset(de->userdata->database, &key) == 0);
+
+ send_entry_removed_signal(de);
+ trigger_save(de->userdata);
+
+ pa_assert_se(pa_hashmap_remove(de->userdata->dbus_entries, de->entry_name));
+ dbus_entry_free(de);
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+#endif /* HAVE_DBUS */
+
static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
struct userdata *u = userdata;
@@ -162,7 +1004,7 @@ static char *get_name(pa_proplist *p, const char *prefix) {
return t;
}
-static struct entry* read_entry(struct userdata *u, const char *name) {
+static struct entry *read_entry(struct userdata *u, const char *name) {
pa_datum key, data;
struct entry *e;
@@ -196,11 +1038,21 @@ static struct entry* read_entry(struct userdata *u, const char *name) {
goto fail;
}
+ if (!memchr(e->card, 0, sizeof(e->card))) {
+ pa_log_warn("Database contains entry for stream %s with missing NUL byte in card name", name);
+ goto fail;
+ }
+
if (e->device_valid && !pa_namereg_is_valid_name(e->device)) {
pa_log_warn("Invalid device name stored in database for stream %s", name);
goto fail;
}
+ if (e->card_valid && !pa_namereg_is_valid_name(e->card)) {
+ pa_log_warn("Invalid card name stored in database for stream %s", name);
+ goto fail;
+ }
+
if (e->volume_valid && !pa_channel_map_valid(&e->channel_map)) {
pa_log_warn("Invalid channel map stored in database for stream %s", name);
goto fail;
@@ -252,6 +1104,10 @@ static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) {
(a->device_valid && strncmp(a->device, b->device, sizeof(a->device))))
return FALSE;
+ if (a->card_valid != b->card_valid ||
+ (a->card_valid && strncmp(a->card, b->card, sizeof(a->card))))
+ return FALSE;
+
if (a->muted_valid != b->muted_valid ||
(a->muted_valid && (a->muted != b->muted)))
return FALSE;
@@ -266,10 +1122,21 @@ static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) {
static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
struct userdata *u = userdata;
- struct entry entry, *old;
- char *name;
+ struct entry entry, *old = NULL;
+ char *name = NULL;
pa_datum key, data;
+ /* These are only used when D-Bus is enabled, but in order to reduce ifdef
+ * clutter these are defined here unconditionally. */
+ pa_bool_t created_new_entry = TRUE;
+ pa_bool_t device_updated = FALSE;
+ pa_bool_t volume_updated = FALSE;
+ pa_bool_t mute_updated = FALSE;
+
+#ifdef HAVE_DBUS
+ struct dbus_entry *de = NULL;
+#endif
+
pa_assert(c);
pa_assert(u);
@@ -291,23 +1158,38 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
if (!(name = get_name(sink_input->proplist, "sink-input")))
return;
- if ((old = read_entry(u, name)))
+ if ((old = read_entry(u, name))) {
entry = *old;
+ created_new_entry = FALSE;
+ }
if (sink_input->save_volume) {
entry.channel_map = sink_input->channel_map;
pa_sink_input_get_volume(sink_input, &entry.volume, FALSE);
entry.volume_valid = TRUE;
+
+ volume_updated = !created_new_entry
+ && (!old->volume_valid
+ || !pa_channel_map_equal(&entry.channel_map, &old->channel_map)
+ || !pa_cvolume_equal(&entry.volume, &old->volume));
}
if (sink_input->save_muted) {
entry.muted = pa_sink_input_get_mute(sink_input);
entry.muted_valid = TRUE;
+
+ mute_updated = !created_new_entry && (!old->muted_valid || entry.muted != old->muted);
}
if (sink_input->save_sink) {
pa_strlcpy(entry.device, sink_input->sink->name, sizeof(entry.device));
entry.device_valid = TRUE;
+
+ device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry.device, old->device));
+ if (sink_input->sink->card) {
+ pa_strlcpy(entry.card, sink_input->sink->card->name, sizeof(entry.card));
+ entry.card_valid = TRUE;
+ }
}
} else {
@@ -321,12 +1203,21 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
if (!(name = get_name(source_output->proplist, "source-output")))
return;
- if ((old = read_entry(u, name)))
+ if ((old = read_entry(u, name))) {
entry = *old;
+ created_new_entry = FALSE;
+ }
if (source_output->save_source) {
pa_strlcpy(entry.device, source_output->source->name, sizeof(entry.device));
entry.device_valid = source_output->save_source;
+
+ device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry.device, old->device));
+
+ if (source_output->source->card) {
+ pa_strlcpy(entry.card, source_output->source->card->name, sizeof(entry.card));
+ entry.card_valid = TRUE;
+ }
}
}
@@ -351,6 +1242,23 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
pa_database_set(u->database, &key, &data, TRUE);
+#ifdef HAVE_DBUS
+ if (created_new_entry) {
+ de = dbus_entry_new(u, name);
+ pa_hashmap_put(u->dbus_entries, de->entry_name, de);
+ send_new_entry_signal(de);
+ } else {
+ pa_assert((de = pa_hashmap_get(u->dbus_entries, name)));
+
+ if (device_updated)
+ send_device_updated_signal(de, &entry);
+ if (volume_updated)
+ send_volume_updated_signal(de, &entry);
+ if (mute_updated)
+ send_mute_updated_signal(de, &entry);
+ }
+#endif
+
pa_xfree(name);
trigger_save(u);
@@ -368,19 +1276,28 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
if (!(name = get_name(new_data->proplist, "sink-input")))
return PA_HOOK_OK;
- if ((e = read_entry(u, name))) {
+ if (new_data->sink)
+ pa_log_debug("Not restoring device for stream %s, because already set.", name);
+ else if ((e = read_entry(u, name))) {
+ pa_sink *s = NULL;
- if (e->device_valid) {
- pa_sink *s;
+ if (e->device_valid)
+ s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK);
- if ((s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK))) {
- if (!new_data->sink) {
- pa_log_info("Restoring device for stream %s.", name);
- new_data->sink = s;
- new_data->save_sink = TRUE;
- } else
- pa_log_debug("Not restoring device for stream %s, because already set.", name);
- }
+ if (!s && e->card_valid) {
+ pa_card *card;
+
+ if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD)))
+ s = pa_idxset_first(card->sinks, NULL);
+ }
+
+ /* It might happen that a stream and a sink are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (s && PA_SINK_IS_LINKED(pa_sink_get_state(s))) {
+ pa_log_info("Restoring device for stream %s.", name);
+ new_data->sink = s;
+ new_data->save_sink = TRUE;
}
pa_xfree(e);
@@ -455,18 +1372,28 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou
if (!(name = get_name(new_data->proplist, "source-output")))
return PA_HOOK_OK;
- if ((e = read_entry(u, name))) {
- pa_source *s;
+ if (new_data->source)
+ pa_log_debug("Not restoring device for stream %s, because already set", name);
+ else if ((e = read_entry(u, name))) {
+ pa_source *s = NULL;
- if (e->device_valid) {
- if ((s = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE))) {
- if (!new_data->source) {
- pa_log_info("Restoring device for stream %s.", name);
- new_data->source = s;
- new_data->save_source = TRUE;
- } else
- pa_log_debug("Not restoring device for stream %s, because already set", name);
- }
+ if (e->device_valid)
+ s = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE);
+
+ if (!s && e->card_valid) {
+ pa_card *card;
+
+ if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD)))
+ s = pa_idxset_first(card->sources, NULL);
+ }
+
+ /* It might happen that a stream and a sink are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (s && PA_SOURCE_IS_LINKED(pa_source_get_state(s))) {
+ pa_log_info("Restoring device for stream %s.", name);
+ new_data->source = s;
+ new_data->save_source = TRUE;
}
pa_xfree(e);
@@ -496,6 +1423,17 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct
if (si->save_sink)
continue;
+ /* Skip this if it is already in the process of being moved
+ * anyway */
+ if (!si->sink)
+ continue;
+
+ /* It might happen that a stream and a sink are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si)))
+ continue;
+
if (!(name = get_name(si->proplist, "sink-input")))
continue;
@@ -534,6 +1472,16 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source,
if (so->direct_on_input)
continue;
+ /* Skip this if it is already in the process of being moved anyway */
+ if (!so->source)
+ continue;
+
+ /* It might happen that a stream and a source are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so)))
+ continue;
+
if (!(name = get_name(so->proplist, "source-input")))
continue;
@@ -567,6 +1515,9 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str
char *name;
struct entry *e;
+ if (!si->sink)
+ continue;
+
if (!(name = get_name(si->proplist, "sink-input")))
continue;
@@ -575,7 +1526,9 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str
if (e->device_valid) {
pa_sink *d;
- if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SINK)) && d != sink)
+ if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SINK)) &&
+ d != sink &&
+ PA_SINK_IS_LINKED(pa_sink_get_state(d)))
pa_sink_input_move_to(si, d, TRUE);
}
@@ -605,6 +1558,12 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc
char *name;
struct entry *e;
+ if (so->direct_on_input)
+ continue;
+
+ if (!so->source)
+ continue;
+
if (!(name = get_name(so->proplist, "source-output")))
continue;
@@ -613,7 +1572,9 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc
if (e->device_valid) {
pa_source *d;
- if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE)) && d != source)
+ if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE)) &&
+ d != source &&
+ PA_SOURCE_IS_LINKED(pa_source_get_state(d)))
pa_source_output_move_to(so, d, TRUE);
}
@@ -811,14 +1772,27 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
mode != PA_UPDATE_SET)
goto fail;
- if (mode == PA_UPDATE_SET)
+ if (mode == PA_UPDATE_SET) {
+#ifdef HAVE_DBUS
+ struct dbus_entry *de;
+ void *state = NULL;
+
+ PA_HASHMAP_FOREACH(de, u->dbus_entries, state) {
+ send_entry_removed_signal(de);
+ dbus_entry_free(pa_hashmap_remove(u->dbus_entries, de->entry_name));
+ }
+#endif
pa_database_clear(u->database);
+ }
while (!pa_tagstruct_eof(t)) {
const char *name, *device;
pa_bool_t muted;
struct entry entry;
pa_datum key, data;
+#ifdef HAVE_DBUS
+ struct entry *old;
+#endif
pa_zero(entry);
entry.version = ENTRY_VERSION;
@@ -850,15 +1824,54 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
!pa_namereg_is_valid_name(entry.device))
goto fail;
+#ifdef HAVE_DBUS
+ old = read_entry(u, name);
+#endif
+
key.data = (char*) name;
key.size = strlen(name);
data.data = &entry;
data.size = sizeof(entry);
- if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0)
+ pa_log_debug("Client %s changes entry %s.",
+ pa_strnull(pa_proplist_gets(pa_native_connection_get_client(c)->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)),
+ name);
+
+ if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0) {
+#ifdef HAVE_DBUS
+ struct dbus_entry *de;
+
+ if (old) {
+ pa_assert_se((de = pa_hashmap_get(u->dbus_entries, name)));
+
+ if ((old->device_valid != entry.device_valid)
+ || (entry.device_valid && !pa_streq(entry.device, old->device)))
+ send_device_updated_signal(de, &entry);
+
+ if ((old->volume_valid != entry.volume_valid)
+ || (entry.volume_valid && (!pa_cvolume_equal(&entry.volume, &old->volume)
+ || !pa_channel_map_equal(&entry.channel_map, &old->channel_map))))
+ send_volume_updated_signal(de, &entry);
+
+ if (!old->muted_valid || (entry.muted != old->muted))
+ send_mute_updated_signal(de, &entry);
+
+ } else {
+ de = dbus_entry_new(u, name);
+ pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de));
+ send_new_entry_signal(de);
+ }
+#endif
+
if (apply_immediately)
apply_entry(u, name, &entry);
+ }
+
+#ifdef HAVE_DBUS
+ if (old)
+ pa_xfree(old);
+#endif
}
trigger_save(u);
@@ -871,10 +1884,20 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
while (!pa_tagstruct_eof(t)) {
const char *name;
pa_datum key;
+#ifdef HAVE_DBUS
+ struct dbus_entry *de;
+#endif
if (pa_tagstruct_gets(t, &name) < 0)
goto fail;
+#ifdef HAVE_DBUS
+ if ((de = pa_hashmap_get(u->dbus_entries, name))) {
+ send_entry_removed_signal(de);
+ dbus_entry_free(pa_hashmap_remove(u->dbus_entries, name));
+ }
+#endif
+
key.data = (char*) name;
key.size = strlen(name);
@@ -933,6 +1956,10 @@ int pa__init(pa_module*m) {
pa_source_output *so;
uint32_t idx;
pa_bool_t restore_device = TRUE, restore_volume = TRUE, restore_muted = TRUE, on_hotplug = TRUE, on_rescue = TRUE;
+#ifdef HAVE_DBUS
+ pa_datum key;
+ pa_bool_t done;
+#endif
pa_assert(m);
@@ -1003,6 +2030,34 @@ int pa__init(pa_module*m) {
pa_log_info("Sucessfully opened database file '%s'.", fname);
pa_xfree(fname);
+#ifdef HAVE_DBUS
+ u->dbus_protocol = pa_dbus_protocol_get(u->core);
+ u->dbus_entries = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, OBJECT_PATH, &stream_restore_interface_info, u) >= 0);
+ pa_assert_se(pa_dbus_protocol_register_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0);
+
+ /* Create the initial dbus entries. */
+ done = !pa_database_first(u->database, &key, NULL);
+ while (!done) {
+ pa_datum next_key;
+ char *name;
+ struct dbus_entry *de;
+
+ done = !pa_database_next(u->database, &key, &next_key, NULL);
+
+ name = pa_xstrndup(key.data, key.size);
+ pa_datum_free(&key);
+
+ de = dbus_entry_new(u, name);
+ pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) >= 0);
+
+ pa_xfree(name);
+
+ key = next_key;
+ }
+#endif
+
PA_IDXSET_FOREACH(si, m->core->sink_inputs, idx)
subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, si->index, u);
@@ -1021,6 +2076,16 @@ fail:
return -1;
}
+#ifdef HAVE_DBUS
+static void free_dbus_entry_cb(void *p, void *userdata) {
+ struct dbus_entry *de = p;
+
+ pa_assert(de);
+
+ dbus_entry_free(de);
+}
+#endif
+
void pa__done(pa_module*m) {
struct userdata* u;
@@ -1029,6 +2094,19 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
+#ifdef HAVE_DBUS
+ if (u->dbus_protocol) {
+ pa_assert(u->dbus_entries);
+
+ pa_assert_se(pa_dbus_protocol_unregister_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0);
+ pa_assert_se(pa_dbus_protocol_remove_interface(u->dbus_protocol, OBJECT_PATH, stream_restore_interface_info.name) >= 0);
+
+ pa_hashmap_free(u->dbus_entries, free_dbus_entry_cb, NULL);
+
+ pa_dbus_protocol_unref(u->dbus_protocol);
+ }
+#endif
+
if (u->subscription)
pa_subscription_free(u->subscription);
diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c
index 70a7b049..7adaa0b1 100644
--- a/src/modules/module-suspend-on-idle.c
+++ b/src/modules/module-suspend-on-idle.c
@@ -145,6 +145,9 @@ static pa_hook_result_t sink_input_fixate_hook_cb(pa_core *c, pa_sink_input_new_
pa_assert(data);
pa_assert(u);
+ if (data->flags & PA_SINK_INPUT_START_CORKED)
+ return PA_HOOK_OK;
+
if ((d = pa_hashmap_get(u->device_infos, data->sink)))
resume(d);
@@ -158,6 +161,9 @@ static pa_hook_result_t source_output_fixate_hook_cb(pa_core *c, pa_source_outpu
pa_assert(data);
pa_assert(u);
+ if (data->flags & PA_SOURCE_OUTPUT_START_CORKED)
+ return PA_HOOK_OK;
+
if (data->source->monitor_of)
d = pa_hashmap_get(u->device_infos, data->source->monitor_of);
else
@@ -226,11 +232,16 @@ static pa_hook_result_t sink_input_move_start_hook_cb(pa_core *c, pa_sink_input
static pa_hook_result_t sink_input_move_finish_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) {
struct device_info *d;
+ pa_sink_input_state_t state;
pa_assert(c);
pa_sink_input_assert_ref(s);
pa_assert(u);
+ state = pa_sink_input_get_state(s);
+ if (state != PA_SINK_INPUT_RUNNING && state != PA_SINK_INPUT_DRAINED)
+ return PA_HOOK_OK;
+
if ((d = pa_hashmap_get(u->device_infos, s->sink)))
resume(d);
@@ -265,6 +276,9 @@ static pa_hook_result_t source_output_move_finish_hook_cb(pa_core *c, pa_source_
pa_source_output_assert_ref(s);
pa_assert(u);
+ if (pa_source_output_get_state(s) != PA_SOURCE_OUTPUT_RUNNING)
+ return PA_HOOK_OK;
+
if (s->source->monitor_of)
d = pa_hashmap_get(u->device_infos, s->source->monitor_of);
else
@@ -279,6 +293,7 @@ static pa_hook_result_t source_output_move_finish_hook_cb(pa_core *c, pa_source_
static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) {
struct device_info *d;
pa_sink_input_state_t state;
+
pa_assert(c);
pa_sink_input_assert_ref(s);
pa_assert(u);
@@ -292,15 +307,11 @@ static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_inp
}
static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) {
- pa_source_output_state_t state;
-
pa_assert(c);
pa_source_output_assert_ref(s);
pa_assert(u);
- state = pa_source_output_get_state(s);
-
- if (state == PA_SOURCE_OUTPUT_RUNNING) {
+ if (pa_source_output_get_state(s) == PA_SOURCE_OUTPUT_RUNNING) {
struct device_info *d;
if (s->source->monitor_of)
@@ -387,22 +398,17 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s
pa_sink *s = PA_SINK(o);
pa_sink_state_t state = pa_sink_get_state(s);
- if (pa_sink_check_suspend(s) <= 0) {
-
+ if (pa_sink_check_suspend(s) <= 0)
if (PA_SINK_IS_OPENED(state))
restart(d);
- }
-
} else if (pa_source_isinstance(o)) {
pa_source *s = PA_SOURCE(o);
pa_source_state_t state = pa_source_get_state(s);
- if (pa_source_check_suspend(s) <= 0) {
-
+ if (pa_source_check_suspend(s) <= 0)
if (PA_SOURCE_IS_OPENED(state))
restart(d);
- }
}
return PA_HOOK_OK;
diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
index f788f660..c97de3a1 100644
--- a/src/modules/module-tunnel.c
+++ b/src/modules/module-tunnel.c
@@ -332,7 +332,7 @@ static void command_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa
static void command_stream_buffer_attr_changed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
struct userdata *u = userdata;
- uint32_t channel, maxlength, tlength, fragsize, prebuf, minreq;
+ uint32_t channel, maxlength, tlength = 0, fragsize, prebuf, minreq;
pa_usec_t usec;
pa_assert(pd);
@@ -1069,6 +1069,33 @@ static void sink_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_t
}
}
+ if (u->version >= 16) {
+ uint32_t n_ports;
+ const char *s;
+
+ if (pa_tagstruct_getu32(t, &n_ports)) {
+ pa_log("Parse failure");
+ goto fail;
+ }
+
+ for (uint32_t j = 0; j < n_ports; j++) {
+ uint32_t priority;
+
+ if (pa_tagstruct_gets(t, &s) < 0 || /* name */
+ pa_tagstruct_gets(t, &s) < 0 || /* description */
+ pa_tagstruct_getu32(t, &priority) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
+ if (pa_tagstruct_gets(t, &s) < 0) { /* active port */
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
if (!pa_tagstruct_eof(t)) {
pa_log("Packet too long");
goto fail;
@@ -1097,7 +1124,7 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag
uint32_t idx, owner_module, client, sink;
pa_usec_t buffer_usec, sink_usec;
const char *name, *driver, *resample_method;
- pa_bool_t mute;
+ pa_bool_t mute = FALSE;
pa_sample_spec sample_spec;
pa_channel_map channel_map;
pa_cvolume volume;
@@ -1162,13 +1189,13 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag
pa_assert(u->sink);
if ((u->version < 11 || !!mute == !!u->sink->muted) &&
- pa_cvolume_equal(&volume, &u->sink->virtual_volume))
+ pa_cvolume_equal(&volume, &u->sink->real_volume))
return;
- pa_sink_volume_changed(u->sink, &volume, FALSE);
+ pa_sink_volume_changed(u->sink, &volume);
if (u->version >= 11)
- pa_sink_mute_changed(u->sink, mute, FALSE);
+ pa_sink_mute_changed(u->sink, mute);
return;
@@ -1245,6 +1272,33 @@ static void source_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa
}
}
+ if (u->version >= 16) {
+ uint32_t n_ports;
+ const char *s;
+
+ if (pa_tagstruct_getu32(t, &n_ports)) {
+ pa_log("Parse failure");
+ goto fail;
+ }
+
+ for (uint32_t j = 0; j < n_ports; j++) {
+ uint32_t priority;
+
+ if (pa_tagstruct_gets(t, &s) < 0 || /* name */
+ pa_tagstruct_gets(t, &s) < 0 || /* description */
+ pa_tagstruct_getu32(t, &priority) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
+ if (pa_tagstruct_gets(t, &s) < 0) { /* active port */
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
if (!pa_tagstruct_eof(t)) {
pa_log("Packet too long");
goto fail;
@@ -1345,12 +1399,11 @@ static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32
/* Called from main context */
static void start_subscribe(struct userdata *u) {
pa_tagstruct *t;
- uint32_t tag;
pa_assert(u);
t = pa_tagstruct_new(NULL, 0);
pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE);
- pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, u->ctag++);
pa_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SERVER|
#ifdef TUNNEL_SINK
PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SINK
@@ -1526,7 +1579,7 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t
reply = pa_tagstruct_new(NULL, 0);
pa_tagstruct_putu32(reply, PA_COMMAND_SET_CLIENT_NAME);
- pa_tagstruct_putu32(reply, tag = u->ctag++);
+ pa_tagstruct_putu32(reply, u->ctag++);
if (u->version >= 13) {
pa_proplist *pl;
@@ -1753,7 +1806,6 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
static void sink_set_volume(pa_sink *sink) {
struct userdata *u;
pa_tagstruct *t;
- uint32_t tag;
pa_assert(sink);
u = sink->userdata;
@@ -1761,9 +1813,9 @@ static void sink_set_volume(pa_sink *sink) {
t = pa_tagstruct_new(NULL, 0);
pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_VOLUME);
- pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, u->ctag++);
pa_tagstruct_putu32(t, u->device_index);
- pa_tagstruct_put_cvolume(t, &sink->virtual_volume);
+ pa_tagstruct_put_cvolume(t, &sink->real_volume);
pa_pstream_send_tagstruct(u->pstream, t);
}
@@ -1771,7 +1823,6 @@ static void sink_set_volume(pa_sink *sink) {
static void sink_set_mute(pa_sink *sink) {
struct userdata *u;
pa_tagstruct *t;
- uint32_t tag;
pa_assert(sink);
u = sink->userdata;
@@ -1782,7 +1833,7 @@ static void sink_set_mute(pa_sink *sink) {
t = pa_tagstruct_new(NULL, 0);
pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_MUTE);
- pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, u->ctag++);
pa_tagstruct_putu32(t, u->device_index);
pa_tagstruct_put_boolean(t, !!sink->muted);
pa_pstream_send_tagstruct(u->pstream, t);
diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c
index 11de1ccb..a12f7c93 100644
--- a/src/modules/module-udev-detect.c
+++ b/src/modules/module-udev-detect.c
@@ -25,13 +25,17 @@
#include <errno.h>
#include <limits.h>
+#include <dirent.h>
#include <sys/inotify.h>
#include <libudev.h>
+#include <pulse/timeval.h>
+
#include <pulsecore/modargs.h>
#include <pulsecore/core-error.h>
#include <pulsecore/core-util.h>
#include <pulsecore/namereg.h>
+#include <pulsecore/ratelimit.h>
#include "module-udev-detect-symdef.h"
@@ -39,18 +43,25 @@ PA_MODULE_AUTHOR("Lennart Poettering");
PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE(
+ "tsched=<enable system timer based scheduling mode?> "
+ "ignore_dB=<ignore dB information from the device?>");
struct device {
char *path;
- pa_bool_t accessible;
+ pa_bool_t need_verify;
char *card_name;
+ char *args;
uint32_t module;
+ pa_ratelimit ratelimit;
};
struct userdata {
pa_core *core;
pa_hashmap *devices;
- pa_bool_t use_tsched;
+
+ pa_bool_t use_tsched:1;
+ pa_bool_t ignore_dB:1;
struct udev* udev;
struct udev_monitor *monitor;
@@ -62,6 +73,7 @@ struct userdata {
static const char* const valid_modargs[] = {
"tsched",
+ "ignore_dB",
NULL
};
@@ -72,6 +84,7 @@ static void device_free(struct device *d) {
pa_xfree(d->path);
pa_xfree(d->card_name);
+ pa_xfree(d->args);
pa_xfree(d);
}
@@ -90,30 +103,196 @@ static const char *path_get_card_id(const char *path) {
return e + 5;
}
+static pa_bool_t is_card_busy(const char *id) {
+ char *card_path = NULL, *pcm_path = NULL, *sub_status = NULL;
+ DIR *card_dir = NULL, *pcm_dir = NULL;
+ FILE *status_file = NULL;
+ size_t len;
+ struct dirent *space = NULL, *de;
+ pa_bool_t busy = FALSE;
+ int r;
+
+ pa_assert(id);
+
+ /* This simply uses /proc/asound/card.../pcm.../sub.../status to
+ * check whether there is still a process using this audio device. */
+
+ card_path = pa_sprintf_malloc("/proc/asound/card%s", id);
+
+ if (!(card_dir = opendir(card_path))) {
+ pa_log_warn("Failed to open %s: %s", card_path, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ len = offsetof(struct dirent, d_name) + fpathconf(dirfd(card_dir), _PC_NAME_MAX) + 1;
+ space = pa_xmalloc(len);
+
+ for (;;) {
+ de = NULL;
+
+ if ((r = readdir_r(card_dir, space, &de)) != 0) {
+ pa_log_warn("readdir_r() failed: %s", pa_cstrerror(r));
+ goto fail;
+ }
+
+ if (!de)
+ break;
+
+ if (!pa_startswith(de->d_name, "pcm"))
+ continue;
+
+ pa_xfree(pcm_path);
+ pcm_path = pa_sprintf_malloc("%s/%s", card_path, de->d_name);
+
+ if (pcm_dir)
+ closedir(pcm_dir);
+
+ if (!(pcm_dir = opendir(pcm_path))) {
+ pa_log_warn("Failed to open %s: %s", pcm_path, pa_cstrerror(errno));
+ continue;
+ }
+
+ for (;;) {
+ char line[32];
+
+ if ((r = readdir_r(pcm_dir, space, &de)) != 0) {
+ pa_log_warn("readdir_r() failed: %s", pa_cstrerror(r));
+ goto fail;
+ }
+
+ if (!de)
+ break;
+
+ if (!pa_startswith(de->d_name, "sub"))
+ continue;
+
+ pa_xfree(sub_status);
+ sub_status = pa_sprintf_malloc("%s/%s/status", pcm_path, de->d_name);
+
+ if (status_file)
+ fclose(status_file);
+
+ if (!(status_file = pa_fopen_cloexec(sub_status, "r"))) {
+ pa_log_warn("Failed to open %s: %s", sub_status, pa_cstrerror(errno));
+ continue;
+ }
+
+ if (!(fgets(line, sizeof(line)-1, status_file))) {
+ pa_log_warn("Failed to read from %s: %s", sub_status, pa_cstrerror(errno));
+ continue;
+ }
+
+ if (!pa_streq(line, "closed\n")) {
+ busy = TRUE;
+ break;
+ }
+ }
+ }
+
+fail:
+
+ pa_xfree(card_path);
+ pa_xfree(pcm_path);
+ pa_xfree(sub_status);
+ pa_xfree(space);
+
+ if (card_dir)
+ closedir(card_dir);
+
+ if (pcm_dir)
+ closedir(pcm_dir);
+
+ if (status_file)
+ fclose(status_file);
+
+ return busy;
+}
+
static void verify_access(struct userdata *u, struct device *d) {
char *cd;
pa_card *card;
+ pa_bool_t accessible;
pa_assert(u);
pa_assert(d);
- if (!(card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD)))
- return;
-
cd = pa_sprintf_malloc("%s/snd/controlC%s", udev_get_dev_path(u->udev), path_get_card_id(d->path));
- d->accessible = access(cd, W_OK) >= 0;
- pa_log_info("%s is accessible: %s", cd, pa_yes_no(d->accessible));
+ accessible = access(cd, R_OK|W_OK) >= 0;
+ pa_log_debug("%s is accessible: %s", cd, pa_yes_no(accessible));
+
pa_xfree(cd);
- pa_card_suspend(card, !d->accessible, PA_SUSPEND_SESSION);
+ if (d->module == PA_INVALID_INDEX) {
+
+ /* If we are not loaded, try to load */
+
+ if (accessible) {
+ pa_module *m;
+ pa_bool_t busy;
+
+ /* Check if any of the PCM devices that belong to this
+ * card are currently busy. If they are, don't try to load
+ * right now, to make sure the probing phase can
+ * successfully complete. When the current user of the
+ * device closes it we will get another notification via
+ * inotify and can then recheck. */
+
+ busy = is_card_busy(path_get_card_id(d->path));
+ pa_log_debug("%s is busy: %s", d->path, pa_yes_no(busy));
+
+ if (!busy) {
+
+ /* So, why do we rate limit here? It's certainly ugly,
+ * but there seems to be no other way. Problem is
+ * this: if we are unable to configure/probe an audio
+ * device after opening it we will close it again and
+ * the module initialization will fail. This will then
+ * cause an inotify event on the device node which
+ * will be forwarded to us. We then try to reopen the
+ * audio device again, practically entering a busy
+ * loop.
+ *
+ * A clean fix would be if we would be able to ignore
+ * our own inotify close events. However, inotify
+ * lacks such functionality. Also, during probing of
+ * the device we cannot really distuingish between
+ * other processes causing EBUSY or ourselves, which
+ * means we have no way to figure out if the probing
+ * during opening was canceled by a "try again"
+ * failure or a "fatal" failure. */
+
+ if (pa_ratelimit_test(&d->ratelimit)) {
+ pa_log_debug("Loading module-alsa-card with arguments '%s'", d->args);
+ m = pa_module_load(u->core, "module-alsa-card", d->args);
+
+ if (m) {
+ d->module = m->index;
+ pa_log_info("Card %s (%s) module loaded.", d->path, d->card_name);
+ } else
+ pa_log_info("Card %s (%s) failed to load module.", d->path, d->card_name);
+ } else
+ pa_log_warn("Tried to configure %s (%s) more often than %u times in %llus",
+ d->path,
+ d->card_name,
+ d->ratelimit.burst,
+ (long long unsigned) (d->ratelimit.interval / PA_USEC_PER_SEC));
+ }
+ }
+
+ } else {
+
+ /* If we are already loaded update suspend status with
+ * accessible boolean */
+
+ if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD)))
+ pa_card_suspend(card, !accessible, PA_SUSPEND_SESSION);
+ }
}
static void card_changed(struct userdata *u, struct udev_device *dev) {
struct device *d;
const char *path;
const char *t;
- char *card_name, *args;
- pa_module *m;
char *n;
pa_assert(u);
@@ -129,42 +308,34 @@ static void card_changed(struct userdata *u, struct udev_device *dev) {
return;
}
+ d = pa_xnew0(struct device, 1);
+ d->path = pa_xstrdup(path);
+ d->module = PA_INVALID_INDEX;
+ PA_INIT_RATELIMIT(d->ratelimit, 10*PA_USEC_PER_SEC, 5);
+
if (!(t = udev_device_get_property_value(dev, "PULSE_NAME")))
if (!(t = udev_device_get_property_value(dev, "ID_ID")))
if (!(t = udev_device_get_property_value(dev, "ID_PATH")))
t = path_get_card_id(path);
n = pa_namereg_make_valid_name(t);
+ d->card_name = pa_sprintf_malloc("alsa_card.%s", n);
+ d->args = pa_sprintf_malloc("device_id=\"%s\" "
+ "name=\"%s\" "
+ "card_name=\"%s\" "
+ "tsched=%s "
+ "ignore_dB=%s "
+ "card_properties=\"module-udev-detect.discovered=1\"",
+ path_get_card_id(path),
+ n,
+ d->card_name,
+ pa_yes_no(u->use_tsched),
+ pa_yes_no(u->ignore_dB));
+ pa_xfree(n);
- card_name = pa_sprintf_malloc("alsa_card.%s", n);
- args = pa_sprintf_malloc("device_id=\"%s\" "
- "name=\"%s\" "
- "card_name=\"%s\" "
- "tsched=%i "
- "card_properties=\"module-udev-detect.discovered=1\"",
- path_get_card_id(path),
- n,
- card_name,
- (int) u->use_tsched);
-
- pa_log_debug("Loading module-alsa-card with arguments '%s'", args);
- m = pa_module_load(u->core, "module-alsa-card", args);
- pa_xfree(args);
-
- if (m) {
- pa_log_info("Card %s (%s) added.", path, n);
-
- d = pa_xnew(struct device, 1);
- d->path = pa_xstrdup(path);
- d->card_name = card_name;
- d->module = m->index;
- d->accessible = TRUE;
-
- pa_hashmap_put(u->devices, d->path, d);
- } else
- pa_xfree(card_name);
+ pa_hashmap_put(u->devices, d->path, d);
- pa_xfree(n);
+ verify_access(u, d);
}
static void remove_card(struct userdata *u, struct udev_device *dev) {
@@ -177,7 +348,10 @@ static void remove_card(struct userdata *u, struct udev_device *dev) {
return;
pa_log_info("Card %s removed.", d->path);
- pa_module_unload_request_by_index(u->core, d->module, TRUE);
+
+ if (d->module != PA_INVALID_INDEX)
+ pa_module_unload_request_by_index(u->core, d->module, TRUE);
+
device_free(d);
}
@@ -254,6 +428,34 @@ fail:
u->udev_io = NULL;
}
+static pa_bool_t pcm_node_belongs_to_device(
+ struct device *d,
+ const char *node) {
+
+ char *cd;
+ pa_bool_t b;
+
+ cd = pa_sprintf_malloc("pcmC%sD", path_get_card_id(d->path));
+ b = pa_startswith(node, cd);
+ pa_xfree(cd);
+
+ return b;
+}
+
+static pa_bool_t control_node_belongs_to_device(
+ struct device *d,
+ const char *node) {
+
+ char *cd;
+ pa_bool_t b;
+
+ cd = pa_sprintf_malloc("controlC%s", path_get_card_id(d->path));
+ b = pa_streq(node, cd);
+ pa_xfree(cd);
+
+ return b;
+}
+
static void inotify_cb(
pa_mainloop_api*a,
pa_io_event* e,
@@ -267,10 +469,13 @@ static void inotify_cb(
} buf;
struct userdata *u = userdata;
static int type = 0;
- pa_bool_t verify = FALSE, deleted = FALSE;
+ pa_bool_t deleted = FALSE;
+ struct device *d;
+ void *state;
for (;;) {
ssize_t r;
+ struct inotify_event *event;
pa_zero(buf);
if ((r = pa_read(fd, &buf, sizeof(buf), &type)) <= 0) {
@@ -282,22 +487,51 @@ static void inotify_cb(
goto fail;
}
- if ((buf.e.mask & IN_CLOSE_WRITE) && pa_startswith(buf.e.name, "pcmC"))
- verify = TRUE;
-
- if ((buf.e.mask & (IN_DELETE_SELF|IN_MOVE_SELF)))
- deleted = TRUE;
+ event = &buf.e;
+ while (r > 0) {
+ size_t len;
+
+ if ((size_t) r < sizeof(struct inotify_event)) {
+ pa_log("read() too short.");
+ goto fail;
+ }
+
+ len = sizeof(struct inotify_event) + event->len;
+
+ if ((size_t) r < len) {
+ pa_log("Payload missing.");
+ goto fail;
+ }
+
+ /* From udev we get the guarantee that the control
+ * device's ACL is changed last. To avoid races when ACLs
+ * are changed we hence watch only the control device */
+ if (((event->mask & IN_ATTRIB) && pa_startswith(event->name, "controlC")))
+ PA_HASHMAP_FOREACH(d, u->devices, state)
+ if (control_node_belongs_to_device(d, event->name))
+ d->need_verify = TRUE;
+
+ /* ALSA doesn't really give us any guarantee on the closing
+ * order, so let's simply hope */
+ if (((event->mask & IN_CLOSE_WRITE) && pa_startswith(event->name, "pcmC")))
+ PA_HASHMAP_FOREACH(d, u->devices, state)
+ if (pcm_node_belongs_to_device(d, event->name))
+ d->need_verify = TRUE;
+
+ /* /dev/snd/ might have been removed */
+ if ((event->mask & (IN_DELETE_SELF|IN_MOVE_SELF)))
+ deleted = TRUE;
+
+ event = (struct inotify_event*) ((uint8_t*) event + len);
+ r -= len;
+ }
}
- if (verify) {
- struct device *d;
- void *state;
-
- pa_log_debug("Verifying access.");
-
- PA_HASHMAP_FOREACH(d, u->devices, state)
+ PA_HASHMAP_FOREACH(d, u->devices, state)
+ if (d->need_verify) {
+ d->need_verify = FALSE;
verify_access(u, d);
- }
+ }
if (!deleted)
return;
@@ -327,7 +561,7 @@ static int setup_inotify(struct userdata *u) {
}
dev_snd = pa_sprintf_malloc("%s/snd", udev_get_dev_path(u->udev));
- r = inotify_add_watch(u->inotify_fd, dev_snd, IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF);
+ r = inotify_add_watch(u->inotify_fd, dev_snd, IN_ATTRIB|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF);
pa_xfree(dev_snd);
if (r < 0) {
@@ -364,6 +598,7 @@ int pa__init(pa_module *m) {
struct udev_enumerate *enumerate = NULL;
struct udev_list_entry *item = NULL, *first = NULL;
int fd;
+ pa_bool_t use_tsched = TRUE, ignore_dB = FALSE;
pa_assert(m);
@@ -375,13 +610,19 @@ int pa__init(pa_module *m) {
m->userdata = u = pa_xnew0(struct userdata, 1);
u->core = m->core;
u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
- u->use_tsched = TRUE;
u->inotify_fd = -1;
- if (pa_modargs_get_value_boolean(ma, "tsched", &u->use_tsched) < 0) {
- pa_log("Failed to parse tsched argument.");
+ if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) {
+ pa_log("Failed to parse tsched= argument.");
+ goto fail;
+ }
+ u->use_tsched = use_tsched;
+
+ if (pa_modargs_get_value_boolean(ma, "ignore_dB", &ignore_dB) < 0) {
+ pa_log("Failed to parse ignore_dB= argument.");
goto fail;
}
+ u->ignore_dB = ignore_dB;
if (!(u->udev = udev_new())) {
pa_log("Failed to initialize udev library.");
@@ -434,7 +675,7 @@ int pa__init(pa_module *m) {
udev_enumerate_unref(enumerate);
- pa_log_info("Loaded %u modules.", pa_hashmap_size(u->devices));
+ pa_log_info("Found %u cards.", pa_hashmap_size(u->devices));
pa_modargs_free(ma);
diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c
index 91da598e..6e484eae 100644
--- a/src/modules/module-volume-restore.c
+++ b/src/modules/module-volume-restore.c
@@ -48,6 +48,7 @@ static const char* const valid_modargs[] = {
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
pa_bool_t restore_device = TRUE, restore_volume = TRUE;
+ pa_module *n;
char *t;
pa_assert(m);
@@ -66,13 +67,15 @@ int pa__init(pa_module*m) {
pa_log_warn("We will now load module-stream-restore. Please make sure to remove module-volume-restore from your configuration.");
t = pa_sprintf_malloc("restore_volume=%s restore_device=%s", pa_yes_no(restore_volume), pa_yes_no(restore_device));
- pa_module_load(m->core, "module-stream-restore", t);
+ n = pa_module_load(m->core, "module-stream-restore", t);
pa_xfree(t);
- pa_module_unload_request(m, TRUE);
+ if (n)
+ pa_module_unload_request(m, TRUE);
pa_modargs_free(ma);
- return 0;
+
+ return n ? 0 : -1;
fail:
if (ma)
diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c
index c44b882b..71536260 100644
--- a/src/modules/oss/module-oss.c
+++ b/src/modules/oss/module-oss.c
@@ -812,11 +812,11 @@ static void sink_get_volume(pa_sink *s) {
pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM));
if (u->mixer_devmask & SOUND_MASK_VOLUME)
- if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0)
+ if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->real_volume) >= 0)
return;
if (u->mixer_devmask & SOUND_MASK_PCM)
- if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->virtual_volume) >= 0)
+ if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->real_volume) >= 0)
return;
pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
@@ -830,11 +830,11 @@ static void sink_set_volume(pa_sink *s) {
pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM));
if (u->mixer_devmask & SOUND_MASK_VOLUME)
- if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0)
+ if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->real_volume) >= 0)
return;
if (u->mixer_devmask & SOUND_MASK_PCM)
- if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->virtual_volume) >= 0)
+ if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->real_volume) >= 0)
return;
pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno));
@@ -848,11 +848,11 @@ static void source_get_volume(pa_source *s) {
pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV));
if (u->mixer_devmask & SOUND_MASK_IGAIN)
- if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0)
+ if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->volume) >= 0)
return;
if (u->mixer_devmask & SOUND_MASK_RECLEV)
- if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0)
+ if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->volume) >= 0)
return;
pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
@@ -866,11 +866,11 @@ static void source_set_volume(pa_source *s) {
pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV));
if (u->mixer_devmask & SOUND_MASK_IGAIN)
- if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0)
+ if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->volume) >= 0)
return;
if (u->mixer_devmask & SOUND_MASK_RECLEV)
- if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0)
+ if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->volume) >= 0)
return;
pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno));
diff --git a/src/modules/oss/oss-util.c b/src/modules/oss/oss-util.c
index 5a109ae9..b95023c3 100644
--- a/src/modules/oss/oss-util.c
+++ b/src/modules/oss/oss-util.c
@@ -55,7 +55,7 @@ int pa_oss_open(const char *device, int *mode, int* pcaps) {
pcaps = &caps;
if (*mode == O_RDWR) {
- if ((fd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) {
+ if ((fd = pa_open_cloexec(device, O_RDWR|O_NDELAY, 0)) >= 0) {
ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0);
if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) {
@@ -71,14 +71,14 @@ int pa_oss_open(const char *device, int *mode, int* pcaps) {
pa_close(fd);
}
- if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY|O_NOCTTY)) < 0) {
- if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY|O_NOCTTY)) < 0) {
+ if ((fd = pa_open_cloexec(device, (*mode = O_WRONLY)|O_NDELAY, 0)) < 0) {
+ if ((fd = pa_open_cloexec(device, (*mode = O_RDONLY)|O_NDELAY, 0)) < 0) {
pa_log("open('%s'): %s", device, pa_cstrerror(errno));
goto fail;
}
}
} else {
- if ((fd = open(device, *mode|O_NDELAY|O_NOCTTY)) < 0) {
+ if ((fd = pa_open_cloexec(device, *mode|O_NDELAY, 0)) < 0) {
pa_log("open('%s'): %s", device, pa_cstrerror(errno));
goto fail;
}
@@ -145,8 +145,6 @@ success:
pa_log_debug("capabilities:%s", t);
pa_xfree(t);
- pa_make_fd_cloexec(fd);
-
return fd;
fail:
@@ -351,9 +349,9 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) {
if ((n = get_device_number(dev)) < 0)
return -1;
- if (!(f = fopen("/dev/sndstat", "r")) &&
- !(f = fopen("/proc/sndstat", "r")) &&
- !(f = fopen("/proc/asound/oss/sndstat", "r"))) {
+ if (!(f = pa_fopen_cloexec("/dev/sndstat", "r")) &&
+ !(f = pa_fopen_cloexec("/proc/sndstat", "r")) &&
+ !(f = pa_fopen_cloexec("/proc/asound/oss/sndstat", "r"))) {
if (errno != ENOENT)
pa_log_warn("failed to open OSS sndstat device: %s", pa_cstrerror(errno));
@@ -403,7 +401,7 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) {
static int open_mixer(const char *mixer) {
int fd;
- if ((fd = open(mixer, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0)
+ if ((fd = pa_open_cloexec(mixer, O_RDWR|O_NDELAY, 0)) >= 0)
return fd;
return -1;
diff --git a/src/modules/raop/base64.c b/src/modules/raop/base64.c
index e1cbed02..5b061034 100644
--- a/src/modules/raop/base64.c
+++ b/src/modules/raop/base64.c
@@ -57,7 +57,6 @@ int pa_base64_encode(const void *data, int size, char **str)
p = s = pa_xnew(char, size * 4 / 3 + 4);
q = (const unsigned char *) data;
- i = 0;
for (i = 0; i < size;) {
c = q[i++];
c *= 256;
diff --git a/src/modules/raop/module-raop-discover.c b/src/modules/raop/module-raop-discover.c
index eaeb77fc..adba8e4f 100644
--- a/src/modules/raop/module-raop-discover.c
+++ b/src/modules/raop/module-raop-discover.c
@@ -265,7 +265,7 @@ static void browser_cb(
struct tunnel *t2;
if ((t2 = pa_hashmap_get(u->tunnels, t))) {
- pa_module_unload_by_index(u->core, t2->module_index, TRUE);
+ pa_module_unload_request_by_index(u->core, t2->module_index, TRUE);
pa_hashmap_remove(u->tunnels, t2);
tunnel_free(t2);
}
@@ -386,7 +386,7 @@ void pa__done(pa_module*m) {
struct tunnel *t;
while ((t = pa_hashmap_steal_first(u->tunnels))) {
- pa_module_unload_by_index(u->core, t->module_index, TRUE);
+ pa_module_unload_request_by_index(u->core, t->module_index, TRUE);
tunnel_free(t);
}
diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c
index 9699132d..ac48ab10 100644
--- a/src/modules/raop/module-raop-sink.c
+++ b/src/modules/raop/module-raop-sink.c
@@ -283,15 +283,15 @@ static void sink_set_volume_cb(pa_sink *s) {
/* Calculate the max volume of all channels.
We'll use this as our (single) volume on the APEX device and emulate
any variation in channel volumes in software */
- v = pa_cvolume_max(&s->virtual_volume);
+ v = pa_cvolume_max(&s->real_volume);
/* Create a pa_cvolume version of our single value */
pa_cvolume_set(&hw, s->sample_spec.channels, v);
/* Perform any software manipulation of the volume needed */
- pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &hw);
+ pa_sw_cvolume_divide(&s->soft_volume, &s->real_volume, &hw);
- pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume));
+ pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->real_volume));
pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &hw));
pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume));
diff --git a/src/modules/reserve-monitor.c b/src/modules/reserve-monitor.c
index 13ecde2b..ab453e61 100644
--- a/src/modules/reserve-monitor.c
+++ b/src/modules/reserve-monitor.c
@@ -38,6 +38,7 @@ struct rm_monitor {
char *device_name;
char *service_name;
+ char *match;
DBusConnection *connection;
@@ -51,12 +52,18 @@ struct rm_monitor {
#define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
+#define SERVICE_FILTER \
+ "type='signal'," \
+ "sender='" DBUS_SERVICE_DBUS "'," \
+ "interface='" DBUS_INTERFACE_DBUS "'," \
+ "member='NameOwnerChanged'," \
+ "arg0='%s'"
+
static DBusHandlerResult filter_handler(
DBusConnection *c,
DBusMessage *s,
void *userdata) {
- DBusMessage *reply;
rm_monitor *m;
DBusError error;
@@ -97,31 +104,10 @@ static DBusHandlerResult filter_handler(
}
}
- return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
invalid:
- if (!(reply = dbus_message_new_error(
- s,
- DBUS_ERROR_INVALID_ARGS,
- "Invalid arguments")))
- goto oom;
-
- if (!dbus_connection_send(c, reply, NULL))
- goto oom;
-
- dbus_message_unref(reply);
-
dbus_error_free(&error);
- return DBUS_HANDLER_RESULT_HANDLED;
-
-oom:
- if (reply)
- dbus_message_unref(reply);
-
- dbus_error_free(&error);
-
- return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
int rm_watch(
@@ -175,11 +161,13 @@ int rm_watch(
m->filtering = 1;
- dbus_bus_add_match(m->connection,
- "type='signal',"
- "sender='" DBUS_SERVICE_DBUS "',"
- "interface='" DBUS_INTERFACE_DBUS "',"
- "member='NameOwnerChanged'", error);
+ if (!(m->match = malloc(sizeof(SERVICE_FILTER) - 2 + strlen(m->service_name)))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ sprintf(m->match, SERVICE_FILTER, m->service_name);
+ dbus_bus_add_match(m->connection, m->match, error);
if (dbus_error_is_set(error)) {
r = -EIO;
@@ -220,10 +208,8 @@ void rm_release(rm_monitor *m) {
if (m->matching)
dbus_bus_remove_match(
m->connection,
- "type='signal',"
- "sender='" DBUS_SERVICE_DBUS "',"
- "interface='" DBUS_INTERFACE_DBUS "',"
- "member='NameOwnerChanged'", NULL);
+ m->match,
+ NULL);
if (m->filtering)
dbus_connection_remove_filter(
@@ -233,6 +219,7 @@ void rm_release(rm_monitor *m) {
free(m->device_name);
free(m->service_name);
+ free(m->match);
if (m->connection)
dbus_connection_unref(m->connection);
diff --git a/src/modules/reserve-wrap.c b/src/modules/reserve-wrap.c
index 6086fc99..4be19c73 100644
--- a/src/modules/reserve-wrap.c
+++ b/src/modules/reserve-wrap.c
@@ -137,7 +137,7 @@ pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name)
#ifdef HAVE_DBUS
if (!(r->connection = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) {
- pa_log_warn("Unable to contact D-Bus session bus: %s: %s", error.name, error.message);
+ pa_log_debug("Unable to contact D-Bus session bus: %s: %s", error.name, error.message);
/* We don't treat this as error here because we want allow PA
* to run even when no session bus is available. */
@@ -154,10 +154,10 @@ pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name)
NULL)) < 0) {
if (k == -EBUSY) {
- pa_log_error("Device '%s' already locked.", device_name);
+ pa_log_debug("Device '%s' already locked.", device_name);
goto fail;
} else {
- pa_log_warn("Failed to acquire reservation lock on device '%s': %s", device_name, pa_cstrerror(-k));
+ pa_log_debug("Failed to acquire reservation lock on device '%s': %s", device_name, pa_cstrerror(-k));
return r;
}
}
@@ -280,7 +280,7 @@ pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const cha
#ifdef HAVE_DBUS
if (!(w->connection = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) {
- pa_log_warn("Unable to contact D-Bus session bus: %s: %s", error.name, error.message);
+ pa_log_debug("Unable to contact D-Bus session bus: %s: %s", error.name, error.message);
/* We don't treat this as error here because we want allow PA
* to run even when no session bus is available. */
@@ -294,7 +294,7 @@ pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const cha
change_cb,
NULL)) < 0) {
- pa_log_warn("Failed to create watch on device '%s': %s", device_name, pa_cstrerror(-k));
+ pa_log_debug("Failed to create watch on device '%s': %s", device_name, pa_cstrerror(-k));
goto fail;
}
diff --git a/src/modules/reserve.c b/src/modules/reserve.c
index 5597f177..b4c168cf 100644
--- a/src/modules/reserve.c
+++ b/src/modules/reserve.c
@@ -291,7 +291,6 @@ static DBusHandlerResult filter_handler(
DBusMessage *m,
void *userdata) {
- DBusMessage *reply;
rd_device *d;
DBusError error;
@@ -323,35 +322,13 @@ static DBusHandlerResult filter_handler(
rd_release(d);
}
- return DBUS_HANDLER_RESULT_HANDLED;
}
}
- return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
invalid:
- if (!(reply = dbus_message_new_error(
- m,
- DBUS_ERROR_INVALID_ARGS,
- "Invalid arguments")))
- goto oom;
-
- if (!dbus_connection_send(c, reply, NULL))
- goto oom;
-
- dbus_message_unref(reply);
-
- dbus_error_free(&error);
-
- return DBUS_HANDLER_RESULT_HANDLED;
-
-oom:
- if (reply)
- dbus_message_unref(reply);
-
dbus_error_free(&error);
- return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c
index 5caf8272..7dbb1efa 100644
--- a/src/modules/rtp/module-rtp-recv.c
+++ b/src/modules/rtp/module-rtp-recv.c
@@ -361,7 +361,7 @@ static void sink_input_attach(pa_sink_input *i) {
pa_assert_se(s = i->userdata);
pa_assert(!s->rtpoll_item);
- s->rtpoll_item = pa_rtpoll_item_new(i->sink->rtpoll, PA_RTPOLL_LATE, 1);
+ s->rtpoll_item = pa_rtpoll_item_new(i->sink->thread_info.rtpoll, PA_RTPOLL_LATE, 1);
p = pa_rtpoll_item_get_pollfd(s->rtpoll_item, NULL);
p->fd = s->rtp_context.fd;
@@ -390,7 +390,7 @@ static int mcast_socket(const struct sockaddr* sa, socklen_t salen) {
pa_assert(salen > 0);
af = sa->sa_family;
- if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) {
+ if ((fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) {
pa_log("Failed to create socket: %s", pa_cstrerror(errno));
goto fail;
}
@@ -501,8 +501,9 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in
pa_proplist_setf(data.proplist, "rtp.payload", "%u", (unsigned) sdp_info->payload);
data.module = u->module;
pa_sink_input_new_data_set_sample_spec(&data, &sdp_info->sample_spec);
+ data.flags = PA_SINK_INPUT_VARIABLE_RATE;
- pa_sink_input_new(&s->sink_input, u->module->core, &data, PA_SINK_INPUT_VARIABLE_RATE);
+ pa_sink_input_new(&s->sink_input, u->module->core, &data);
pa_sink_input_new_data_done(&data);
if (!s->sink_input) {
diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c
index f147364d..ab815223 100644
--- a/src/modules/rtp/module-rtp-send.c
+++ b/src/modules/rtp/module-rtp-send.c
@@ -262,7 +262,7 @@ int pa__init(pa_module*m) {
goto fail;
}
- if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) {
+ if ((fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) {
pa_log("socket() failed: %s", pa_cstrerror(errno));
goto fail;
}
@@ -277,7 +277,7 @@ int pa__init(pa_module*m) {
#endif
}
- if ((sap_fd = socket(af, SOCK_DGRAM, 0)) < 0) {
+ if ((sap_fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) {
pa_log("socket() failed: %s", pa_cstrerror(errno));
goto fail;
}
@@ -316,8 +316,6 @@ int pa__init(pa_module*m) {
/* If the socket queue is full, let's drop packets */
pa_make_fd_nonblock(fd);
pa_make_udp_socket_low_delay(fd);
- pa_make_fd_cloexec(fd);
- pa_make_fd_cloexec(sap_fd);
pa_source_output_new_data_init(&data);
pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, "RTP Monitor Stream");
@@ -330,8 +328,9 @@ int pa__init(pa_module*m) {
data.source = s;
pa_source_output_new_data_set_sample_spec(&data, &ss);
pa_source_output_new_data_set_channel_map(&data, &cm);
+ data.flags = PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND;
- pa_source_output_new(&o, m->core, &data, PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND);
+ pa_source_output_new(&o, m->core, &data);
pa_source_output_new_data_done(&data);
if (!o) {
diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c
index 72d304e8..59618064 100644
--- a/src/modules/rtp/rtsp_client.c
+++ b/src/modules/rtp/rtsp_client.c
@@ -60,7 +60,6 @@ struct pa_rtsp_client {
uint16_t port;
pa_socket_client *sc;
- pa_iochannel *io;
pa_ioline *ioline;
pa_rtsp_cb_t callback;
@@ -111,10 +110,8 @@ void pa_rtsp_client_free(pa_rtsp_client* c) {
if (c->sc)
pa_socket_client_unref(c->sc);
- if (c->ioline)
- pa_ioline_close(c->ioline);
- else if (c->io)
- pa_iochannel_free(c->io);
+
+ pa_rtsp_disconnect(c);
pa_xfree(c->hostname);
pa_xfree(c->url);
@@ -187,7 +184,6 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
if (!s) {
/* Keep the ioline/iochannel open as they will be freed automatically */
c->ioline = NULL;
- c->io = NULL;
c->callback(c, STATE_DISCONNECTED, NULL, c->userdata);
return;
}
@@ -214,11 +210,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
/* End of headers */
/* We will have a header left from our looping iteration, so add it in :) */
if (c->last_header) {
+ char *tmp = pa_strbuf_tostring_free(c->header_buffer);
/* This is not a continuation header so let's dump it into our proplist */
- pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer));
+ pa_headerlist_puts(c->response_headers, c->last_header, tmp);
+ pa_xfree(tmp);
pa_xfree(c->last_header);
c->last_header = NULL;
- c->header_buffer= NULL;
+ c->header_buffer = NULL;
}
pa_log_debug("Full response received. Dispatching");
@@ -240,9 +238,11 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
}
if (c->last_header) {
+ char *tmp = pa_strbuf_tostring_free(c->header_buffer);
/* This is not a continuation header so let's dump the full
header/value into our proplist */
- pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer));
+ pa_headerlist_puts(c->response_headers, c->last_header, tmp);
+ pa_xfree(tmp);
pa_xfree(c->last_header);
c->last_header = NULL;
c->header_buffer = NULL;
@@ -299,8 +299,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
pa_log("Connection failed: %s", pa_cstrerror(errno));
return;
}
- pa_assert(!c->io);
- c->io = io;
+ pa_assert(!c->ioline);
c->ioline = pa_ioline_new(io);
pa_ioline_set_callback(c->ioline, line_callback, c);
@@ -356,9 +355,6 @@ void pa_rtsp_disconnect(pa_rtsp_client *c) {
if (c->ioline)
pa_ioline_close(c->ioline);
- else if (c->io)
- pa_iochannel_free(c->io);
- c->io = NULL;
c->ioline = NULL;
}
@@ -404,13 +400,11 @@ static int rtsp_exec(pa_rtsp_client* c, const char* cmd,
pa_headerlist* headers) {
pa_strbuf* buf;
char* hdrs;
- ssize_t l;
pa_assert(c);
pa_assert(c->url);
-
- if (!cmd)
- return -1;
+ pa_assert(cmd);
+ pa_assert(c->ioline);
pa_log_debug("Sending command: %s", cmd);
@@ -449,7 +443,7 @@ static int rtsp_exec(pa_rtsp_client* c, const char* cmd,
hdrs = pa_strbuf_tostring_free(buf);
/*pa_log_debug("Submitting request:");
pa_log_debug(hdrs);*/
- l = pa_iochannel_write(c->io, hdrs, strlen(hdrs));
+ pa_ioline_puts(c->ioline, hdrs);
pa_xfree(hdrs);
return 0;
diff --git a/src/modules/x11/module-x11-publish.c b/src/modules/x11/module-x11-publish.c
index 2c7fdc12..7ee1b6da 100644
--- a/src/modules/x11/module-x11-publish.c
+++ b/src/modules/x11/module-x11-publish.c
@@ -90,7 +90,7 @@ static void publish_servers(struct userdata *u, pa_strlist *l) {
l = pa_strlist_reverse(l);
s = pa_strlist_tostring(l);
- l = pa_strlist_reverse(l);
+ pa_strlist_reverse(l);
pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER", s);
pa_xfree(s);