diff --git a/man/pacat.1.xml.in b/man/pacat.1.xml.in
index 387f3ed4e..81ca532bc 100644
--- a/man/pacat.1.xml.in
+++ b/man/pacat.1.xml.in
@@ -148,6 +148,14 @@ License along with PulseAudio; if not, see .
aux31 .
+
enable-remixing= If disabled never upmix or
downmix channels to different channel maps. Instead, do a simple
diff --git a/shell-completion/bash/pactl b/shell-completion/bash/pactl
index 050fe7d58..bcdda312b 100644
--- a/shell-completion/bash/pactl
+++ b/shell-completion/bash/pactl
@@ -438,7 +438,7 @@ _pacat () {
local cur prev comps
local flags='-h --help --version -r --record -p --playback -v --verbose -s
--server= -d --device= -n --client-name= --stream-name= --volume=
- --rate= --format= --channels= --channel-map= --fix-format --fix-rate
+ --rate= --format= --channels= --channel-map= --encoding= --fix-format --fix-rate
--fix-channels --no-remix --no-remap --latency= --process-time=
--latency-msec= --process-time-msec= --property= --raw --passthrough
--file-format= --list-file-formats --monitor-stream='
diff --git a/shell-completion/zsh/_pulseaudio b/shell-completion/zsh/_pulseaudio
index aaa2ccfc0..262410f83 100644
--- a/shell-completion/zsh/_pulseaudio
+++ b/shell-completion/zsh/_pulseaudio
@@ -680,6 +680,7 @@ _pacat_completion() {
'--format=[sample type to use]:format:((${(q)_pacat_sample_formats}))' \
'--channels=[number of channels to use]:number:(1 2)' \
'--channel-map=[channel map to use]:map' \
+ '--encoding=[encoding to use for non-PCM audio]:encoding' \
'--fix-format[use the sample format of the sink]' \
'--fix-rate[use the rate of the sink]' \
'--fix-channels[channel map of the sink]' \
diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
index ffef554be..7a75be04e 100644
--- a/src/daemon/daemon-conf.c
+++ b/src/daemon/daemon-conf.c
@@ -83,6 +83,7 @@ static const pa_daemon_conf default_conf = {
.log_time = false,
.resample_method = PA_RESAMPLER_AUTO,
.avoid_resampling = false,
+ .avoid_processing = false,
.disable_remixing = false,
.remixing_use_all_sink_channels = true,
.remixing_produce_lfe = false,
@@ -606,6 +607,7 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
pa_config_parse_int, &c->deferred_volume_extra_delay_usec, NULL },
{ "nice-level", parse_nice_level, c, NULL },
{ "avoid-resampling", pa_config_parse_bool, &c->avoid_resampling, NULL },
+ { "avoid-processing", pa_config_parse_bool, &c->avoid_processing, NULL },
{ "disable-remixing", pa_config_parse_bool, &c->disable_remixing, NULL },
{ "enable-remixing", pa_config_parse_not_bool, &c->disable_remixing, NULL },
{ "remixing-use-all-sink-channels",
@@ -819,6 +821,7 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
pa_strbuf_printf(s, "log-level = %s\n", log_level_to_string[c->log_level]);
pa_strbuf_printf(s, "resample-method = %s\n", pa_resample_method_to_string(c->resample_method));
pa_strbuf_printf(s, "avoid-resampling = %s\n", pa_yes_no(c->avoid_resampling));
+ pa_strbuf_printf(s, "avoid-processing = %s\n", pa_yes_no(c->avoid_processing));
pa_strbuf_printf(s, "enable-remixing = %s\n", pa_yes_no(!c->disable_remixing));
pa_strbuf_printf(s, "remixing-use-all-sink-channels = %s\n", pa_yes_no(c->remixing_use_all_sink_channels));
pa_strbuf_printf(s, "remixing-produce-lfe = %s\n", pa_yes_no(c->remixing_produce_lfe));
diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h
index fa713b95d..31a5a8821 100644
--- a/src/daemon/daemon-conf.h
+++ b/src/daemon/daemon-conf.h
@@ -68,6 +68,7 @@ typedef struct pa_daemon_conf {
disable_shm,
disable_memfd,
avoid_resampling,
+ avoid_processing,
disable_remixing,
remixing_use_all_sink_channels,
remixing_produce_lfe,
diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in
index 7409976d1..c8e0bbaae 100644
--- a/src/daemon/daemon.conf.in
+++ b/src/daemon/daemon.conf.in
@@ -55,6 +55,7 @@ ifelse(@HAVE_DBUS@, 1, [dnl
; resample-method = speex-float-1
; avoid-resampling = false
+; avoid-processing = false
; enable-remixing = yes
; remixing-use-all-sink-channels = yes
; remixing-produce-lfe = no
diff --git a/src/daemon/main.c b/src/daemon/main.c
index 924a4d4aa..775cf2cde 100644
--- a/src/daemon/main.c
+++ b/src/daemon/main.c
@@ -1208,6 +1208,7 @@ int main(int argc, char *argv[]) {
c->realtime_priority = conf->realtime_priority;
c->realtime_scheduling = conf->realtime_scheduling;
c->avoid_resampling = conf->avoid_resampling;
+ c->avoid_processing = conf->avoid_processing;
c->disable_remixing = conf->disable_remixing;
c->remixing_use_all_sink_channels = conf->remixing_use_all_sink_channels;
c->remixing_produce_lfe = conf->remixing_produce_lfe;
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index ca22f195f..60c178a63 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -124,6 +124,7 @@ struct userdata {
pa_sample_spec verified_sample_spec;
pa_sample_format_t *supported_formats;
unsigned int *supported_rates;
+ unsigned int *supported_channels;
struct {
size_t fragment_size;
size_t nfrags;
@@ -158,6 +159,7 @@ struct userdata {
char *device_name; /* name of the PCM device */
char *control_device; /* name of the control device */
+ bool passthrough;
bool use_mmap:1, use_tsched:1, deferred_volume:1, fixed_latency_range:1;
@@ -1202,12 +1204,34 @@ static int unsuspend(struct userdata *u, bool recovering) {
pa_log_info("Trying resume...");
- if ((is_iec958(u) || is_hdmi(u)) && pa_sink_is_passthrough(u->sink)) {
+ if ((is_iec958(u) || is_hdmi(u))) {
/* Need to open device in NONAUDIO mode */
- int len = strlen(u->device_name) + 8;
+ int len = strlen(u->device_name) + 50;
+ uint8_t aes3;
+
+ switch (u->sink->sample_spec.rate) {
+ case 22050: aes3 = IEC958_AES3_CON_FS_22050; break;
+ case 24000: aes3 = IEC958_AES3_CON_FS_24000; break;
+ case 32000: aes3 = IEC958_AES3_CON_FS_32000; break;
+ case 44100: aes3 = IEC958_AES3_CON_FS_44100; break;
+ case 48000: aes3 = IEC958_AES3_CON_FS_48000; break;
+ case 88200: aes3 = IEC958_AES3_CON_FS_88200; break;
+ case 96000: aes3 = IEC958_AES3_CON_FS_96000; break;
+ case 176400: aes3 = IEC958_AES3_CON_FS_176400; break;
+ case 192000: aes3 = IEC958_AES3_CON_FS_192000; break;
+ case 768000: aes3 = IEC958_AES3_CON_FS_768000; break;
+ default: aes3 = IEC958_AES3_CON_FS_NOTID; break;
+ }
+
+ if (u->sink->sample_spec.channels == 8)
+ aes3 = IEC958_AES3_CON_FS_768000;
device_name = pa_xmalloc(len);
- pa_snprintf(device_name, len, "%s,AES0=6", u->device_name);
+ pa_snprintf(device_name, len, "%s,AES0=0x%02x,AES1=0x%02x,AES2=0x%02x,AES3=0x%02x", u->device_name,
+ IEC958_AES0_CON_EMPHASIS_NONE | (u->passthrough ? IEC958_AES0_NONAUDIO : 0),
+ IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER,
+ 0, aes3);
+ pa_log_debug("Opening device for passthrough as: %s", device_name);
}
/*
@@ -1817,27 +1841,22 @@ static bool sink_set_formats(pa_sink *s, pa_idxset *formats) {
return true;
}
-static void sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, bool passthrough) {
+static int sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough) {
struct userdata *u = s->userdata;
int i;
bool format_supported = false;
bool rate_supported = false;
-#ifdef USE_SMOOTHER_2
+ bool channels_supported = false;
pa_sample_spec effective_spec;
-#endif
+ pa_channel_map effective_map;
pa_assert(u);
-#ifdef USE_SMOOTHER_2
- effective_spec.channels = s->sample_spec.channels;
-#endif
+ effective_spec = s->sample_spec;
for (i = 0; u->supported_formats[i] != PA_SAMPLE_MAX; i++) {
if (u->supported_formats[i] == spec->format) {
- pa_sink_set_sample_format(u->sink, spec->format);
-#ifdef USE_SMOOTHER_2
effective_spec.format = spec->format;
-#endif
format_supported = true;
break;
}
@@ -1846,18 +1865,12 @@ static void sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, bool passthrou
if (!format_supported) {
pa_log_info("Sink does not support sample format of %s, set it to a verified value",
pa_sample_format_to_string(spec->format));
- pa_sink_set_sample_format(u->sink, u->verified_sample_spec.format);
-#ifdef USE_SMOOTHER_2
effective_spec.format = u->verified_sample_spec.format;
-#endif
}
for (i = 0; u->supported_rates[i]; i++) {
if (u->supported_rates[i] == spec->rate) {
- pa_sink_set_sample_rate(u->sink, spec->rate);
-#ifdef USE_SMOOTHER_2
effective_spec.rate = spec->rate;
-#endif
rate_supported = true;
break;
}
@@ -1865,17 +1878,42 @@ static void sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, bool passthrou
if (!rate_supported) {
pa_log_info("Sink does not support sample rate of %u, set it to a verified value", spec->rate);
- pa_sink_set_sample_rate(u->sink, u->verified_sample_spec.rate);
-#ifdef USE_SMOOTHER_2
effective_spec.rate = u->verified_sample_spec.rate;
-#endif
}
+ for (i = 0; u->supported_channels[i]; i++) {
+ if (u->supported_channels[i] == spec->channels) {
+ effective_spec.channels = spec->channels;
+ channels_supported = true;
+ break;
+ }
+ }
+
+ if (!channels_supported) {
+ pa_log_info("Sink does not support %u channels, set it to a verified value", spec->channels);
+ effective_spec.channels = u->verified_sample_spec.channels;
+ }
+
+ /* We con't actually support configuring the channel map, so let's do the best we can */
+ pa_channel_map_init_auto(&effective_map, effective_spec.channels, PA_CHANNEL_MAP_ALSA);
+ if (!pa_channel_map_equal(map, &effective_map)) {
+ char req_map_str[PA_CHANNEL_MAP_SNPRINT_MAX], eff_map_str[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ pa_log_info("Cannot set channel map to %s, using default of %s",
+ pa_channel_map_snprint(req_map_str, sizeof(req_map_str), map),
+ pa_channel_map_snprint(eff_map_str, sizeof(eff_map_str), &effective_map));
+ }
+
+ pa_sink_set_sample_spec(u->sink, &effective_spec, &effective_map);
+
#ifdef USE_SMOOTHER_2
pa_smoother_2_set_sample_spec(u->smoother, pa_rtclock_now(), &effective_spec);
#endif
/* Passthrough status change is handled during unsuspend */
+ u->passthrough = passthrough;
+
+ return 0;
}
static int process_rewind(struct userdata *u) {
@@ -2346,6 +2384,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
bool b;
bool d;
bool avoid_resampling;
+ bool avoid_processing;
pa_sink_new_data data;
bool volume_is_set;
bool mute_is_set;
@@ -2362,6 +2401,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
ss = m->core->default_sample_spec;
map = m->core->default_channel_map;
avoid_resampling = m->core->avoid_resampling;
+ avoid_processing = m->core->avoid_processing;
/* Pick sample spec overrides from the mapping, if any */
if (mapping) {
@@ -2461,6 +2501,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
u->first = true;
u->rewind_safeguard = rewind_safeguard;
u->rtpoll = pa_rtpoll_new();
+ u->passthrough = false;
if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) {
pa_log("pa_thread_mq_init() failed.");
@@ -2614,6 +2655,12 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
goto fail;
}
+ u->supported_channels = pa_alsa_get_supported_channels(u->pcm_handle, ss.channels);
+ if (!u->supported_channels) {
+ pa_log_error("Failed to find any supported channel counts.");
+ goto fail;
+ }
+
/* ALSA might tweak the sample spec, so recalculate the frame size */
frame_size = pa_frame_size(&ss);
@@ -2642,6 +2689,13 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
}
pa_sink_new_data_set_avoid_resampling(&data, avoid_resampling);
+ if (pa_modargs_get_value_boolean(ma, "avoid_processing", &avoid_processing) < 0) {
+ pa_log("Failed to parse avoid_processing argument.");
+ pa_sink_new_data_done(&data);
+ goto fail;
+ }
+ pa_sink_new_data_set_avoid_processing(&data, avoid_processing);
+
pa_sink_new_data_set_sample_spec(&data, &ss);
pa_sink_new_data_set_channel_map(&data, &map);
pa_sink_new_data_set_alternate_sample_rate(&data, alternate_sample_rate);
@@ -2929,6 +2983,9 @@ static void userdata_free(struct userdata *u) {
if (u->supported_rates)
pa_xfree(u->supported_rates);
+ if (u->supported_channels)
+ pa_xfree(u->supported_channels);
+
reserve_done(u);
monitor_done(u);
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index d88c47f1f..ab1304449 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -112,6 +112,7 @@ struct userdata {
pa_sample_spec verified_sample_spec;
pa_sample_format_t *supported_formats;
unsigned int *supported_rates;
+ unsigned int *supported_channels;
struct {
size_t fragment_size;
size_t nfrags;
@@ -1632,27 +1633,22 @@ static void source_update_requested_latency_cb(pa_source *s) {
update_sw_params(u);
}
-static void source_reconfigure_cb(pa_source *s, pa_sample_spec *spec, bool passthrough) {
+static int source_reconfigure_cb(pa_source *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough) {
struct userdata *u = s->userdata;
int i;
bool format_supported = false;
bool rate_supported = false;
-#ifdef USE_SMOOTHER_2
+ bool channels_supported = false;
pa_sample_spec effective_spec;
-#endif
+ pa_channel_map effective_map;
pa_assert(u);
-#ifdef USE_SMOOTHER_2
- effective_spec.channels = s->sample_spec.channels;
-#endif
+ effective_spec = s->sample_spec;
for (i = 0; u->supported_formats[i] != PA_SAMPLE_MAX; i++) {
if (u->supported_formats[i] == spec->format) {
- pa_source_set_sample_format(u->source, spec->format);
-#ifdef USE_SMOOTHER_2
effective_spec.format = spec->format;
-#endif
format_supported = true;
break;
}
@@ -1661,18 +1657,12 @@ static void source_reconfigure_cb(pa_source *s, pa_sample_spec *spec, bool passt
if (!format_supported) {
pa_log_info("Source does not support sample format of %s, set it to a verified value",
pa_sample_format_to_string(spec->format));
- pa_source_set_sample_format(u->source, u->verified_sample_spec.format);
-#ifdef USE_SMOOTHER_2
effective_spec.format = u->verified_sample_spec.format;
-#endif
}
for (i = 0; u->supported_rates[i]; i++) {
if (u->supported_rates[i] == spec->rate) {
- pa_source_set_sample_rate(u->source, spec->rate);
-#ifdef USE_SMOOTHER_2
effective_spec.rate = spec->rate;
-#endif
rate_supported = true;
break;
}
@@ -1680,16 +1670,39 @@ static void source_reconfigure_cb(pa_source *s, pa_sample_spec *spec, bool passt
if (!rate_supported) {
pa_log_info("Source does not support sample rate of %u, set it to a verfied value", spec->rate);
- pa_source_set_sample_rate(u->source, u->verified_sample_spec.rate);
-#ifdef USE_SMOOTHER_2
effective_spec.rate = u->verified_sample_spec.rate;
-#endif
}
-#ifdef USE_SMOOTHER_2
+ for (i = 0; u->supported_channels[i]; i++) {
+ if (u->supported_channels[i] == spec->channels) {
+ effective_spec.channels = spec->channels;
+ channels_supported = true;
+ break;
+ }
+ }
+
+ if (!channels_supported) {
+ pa_log_info("Sink does not support %u channels, set it to a verified value", spec->channels);
+ effective_spec.channels = u->verified_sample_spec.channels;
+ }
+
+ /* We con't actually support configuring the channel map, so let's do the best we can */
+ pa_channel_map_init_auto(&effective_map, effective_spec.channels, PA_CHANNEL_MAP_ALSA);
+ if (!pa_channel_map_equal(map, &effective_map)) {
+ char req_map_str[PA_CHANNEL_MAP_SNPRINT_MAX], eff_map_str[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ pa_log_info("Cannot set channel map to %s, using default of %s",
+ pa_channel_map_snprint(req_map_str, sizeof(req_map_str), map),
+ pa_channel_map_snprint(eff_map_str, sizeof(eff_map_str), &effective_map));
+ }
+
+ pa_source_set_sample_spec(u->source, &effective_spec, map);
+
+#if USE_SMOOTHER_2
pa_smoother_2_set_sample_spec(u->smoother, pa_rtclock_now(), &effective_spec);
#endif
+ return 0;
}
static void thread_func(void *userdata) {
@@ -2049,6 +2062,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
bool b;
bool d;
bool avoid_resampling;
+ bool avoid_processing;
pa_source_new_data data;
bool volume_is_set;
bool mute_is_set;
@@ -2061,6 +2075,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
ss = m->core->default_sample_spec;
map = m->core->default_channel_map;
avoid_resampling = m->core->avoid_resampling;
+ avoid_processing = m->core->avoid_processing;
/* Pick sample spec overrides from the mapping, if any */
if (mapping) {
@@ -2289,6 +2304,12 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
goto fail;
}
+ u->supported_channels = pa_alsa_get_supported_channels(u->pcm_handle, ss.channels);
+ if (!u->supported_channels) {
+ pa_log_error("Failed to find any supported channel counts.");
+ goto fail;
+ }
+
/* ALSA might tweak the sample spec, so recalculate the frame size */
frame_size = pa_frame_size(&ss);
@@ -2317,6 +2338,13 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
}
pa_source_new_data_set_avoid_resampling(&data, avoid_resampling);
+ if (pa_modargs_get_value_boolean(ma, "avoid_processing", &avoid_processing) < 0) {
+ pa_log("Failed to parse avoid_processing argument.");
+ pa_source_new_data_done(&data);
+ goto fail;
+ }
+ pa_source_new_data_set_avoid_processing(&data, avoid_processing);
+
pa_source_new_data_set_sample_spec(&data, &ss);
pa_source_new_data_set_channel_map(&data, &map);
pa_source_new_data_set_alternate_sample_rate(&data, alternate_sample_rate);
@@ -2543,6 +2571,9 @@ static void userdata_free(struct userdata *u) {
if (u->supported_rates)
pa_xfree(u->supported_rates);
+ if (u->supported_channels)
+ pa_xfree(u->supported_channels);
+
reserve_done(u);
monitor_done(u);
diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c
index d3c092f52..2ba8c8fba 100644
--- a/src/modules/alsa/alsa-util.c
+++ b/src/modules/alsa/alsa-util.c
@@ -1579,6 +1579,52 @@ pa_sample_format_t *pa_alsa_get_supported_formats(snd_pcm_t *pcm, pa_sample_form
return formats;
}
+unsigned int *pa_alsa_get_supported_channels(snd_pcm_t *pcm, unsigned int fallback_channels) {
+ /* Index 0 is unused as "no channels" is meaningless */
+ bool supported[PA_CHANNELS_MAX + 1] = { false, };
+ snd_pcm_hw_params_t *hwparams;
+ unsigned int i, j, n, *channels = NULL;
+ int ret;
+
+ snd_pcm_hw_params_alloca(&hwparams);
+
+ if ((ret = snd_pcm_hw_params_any(pcm, hwparams)) < 0) {
+ pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret));
+ return NULL;
+ }
+
+ for (i = 1, n = 0; i <= PA_CHANNELS_MAX; i++) {
+ if (snd_pcm_hw_params_test_channels(pcm, hwparams, i) == 0) {
+ supported[i] = true;
+ n++;
+ }
+ }
+
+ if (n > 0) {
+ channels = pa_xnew(unsigned int, n + 1);
+
+ for (i = 1, j = 0; i <= PA_CHANNELS_MAX; i++) {
+ if (supported[i])
+ channels[j++] = i;
+ }
+
+ channels[j] = 0;
+ } else {
+ channels = pa_xnew(unsigned int, 2);
+
+ channels[0] = fallback_channels;
+ if ((ret = snd_pcm_hw_params_set_channels_near(pcm, hwparams, &channels[0])) < 0) {
+ pa_log_debug("snd_pcm_hw_params_set_channels_near() failed: %s", pa_alsa_strerror(ret));
+ pa_xfree(channels);
+ return NULL;
+ }
+
+ channels[1] = 0;
+ }
+
+ return channels;
+}
+
bool pa_alsa_pcm_is_hw(snd_pcm_t *pcm) {
snd_pcm_info_t* info;
snd_pcm_info_alloca(&info);
diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h
index c65801104..cffbdd64e 100644
--- a/src/modules/alsa/alsa-util.h
+++ b/src/modules/alsa/alsa-util.h
@@ -148,6 +148,7 @@ char *pa_alsa_get_reserve_name(const char *device);
unsigned int *pa_alsa_get_supported_rates(snd_pcm_t *pcm, unsigned int fallback_rate);
pa_sample_format_t *pa_alsa_get_supported_formats(snd_pcm_t *pcm, pa_sample_format_t fallback_format);
+unsigned int *pa_alsa_get_supported_channels(snd_pcm_t *pcm, unsigned int fallback_channels);
bool pa_alsa_pcm_is_hw(snd_pcm_t *pcm);
bool pa_alsa_pcm_is_modem(snd_pcm_t *pcm);
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index 05c87c6bb..4d53bc0de 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -69,6 +69,7 @@ PA_MODULE_USAGE(
"paths_dir= "
"use_ucm= "
"avoid_resampling= "
+ "avoid_processing= "
"control= "
);
@@ -98,6 +99,7 @@ static const char* const valid_modargs[] = {
"paths_dir",
"use_ucm",
"avoid_resampling",
+ "avoid_processing",
"control",
NULL
};
diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c
index 714a81a63..36477bf58 100644
--- a/src/modules/module-null-sink.c
+++ b/src/modules/module-null-sink.c
@@ -21,9 +21,10 @@
#include
#endif
-#include
-#include
#include
+#include
+#include
+#include
#include
#include
@@ -35,6 +36,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -52,9 +54,12 @@ PA_MODULE_USAGE(
"format= "
"rate= "
"channels= "
- "channel_map="
- "formats="
- "norewinds=");
+ "channel_map= "
+ "formats= "
+ "norewinds= "
+ "dump= "
+ "avoid_processing= "
+);
#define DEFAULT_SINK_NAME "null"
#define BLOCK_USEC (2 * PA_USEC_PER_SEC)
@@ -75,6 +80,8 @@ struct userdata {
pa_idxset *formats;
bool norewinds;
+
+ int dump_fd;
};
static const char* const valid_modargs[] = {
@@ -86,6 +93,8 @@ static const char* const valid_modargs[] = {
"channel_map",
"formats",
"norewinds",
+ "dump",
+ "avoid_processing",
NULL
};
@@ -168,9 +177,12 @@ static void sink_update_requested_latency_cb(pa_sink *s) {
sink_recalculate_max_request_and_rewind(s);
}
-static void sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, bool passthrough) {
- /* We don't need to do anything */
- s->sample_spec = *spec;
+static int sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough) {
+ pa_sink_assert_ref(s);
+
+ pa_sink_set_sample_spec(s, spec, map);
+
+ return 0;
}
static bool sink_set_formats_cb(pa_sink *s, pa_idxset *formats) {
@@ -247,6 +259,25 @@ static void process_render(struct userdata *u, pa_usec_t now) {
request_size = PA_MIN(request_size, u->sink->thread_info.max_request);
pa_sink_render(u->sink, request_size, &chunk);
+ if (u->dump_fd >= 0) {
+ void *p;
+ size_t l = 0;
+
+ p = pa_memblock_acquire(chunk.memblock);
+
+ while (l < chunk.length) {
+ ssize_t ret = pa_write(u->dump_fd, (uint8_t*) p + chunk.index + l, chunk.length - l, NULL);
+ if (ret < 0) {
+ pa_log_error("Failed to write data to dump file: %s", pa_cstrerror(ret));
+ break;
+ }
+
+ l += ret;
+ }
+
+ pa_memblock_release(chunk.memblock);
+ }
+
pa_memblock_unref(chunk.memblock);
/* pa_log_debug("Ate %lu bytes.", (unsigned long) chunk.length); */
@@ -319,8 +350,9 @@ int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
pa_sink_new_data data;
pa_format_info *format;
- const char *formats;
+ const char *formats, *dump_file;
size_t nbytes;
+ bool avoid_processing;
pa_assert(m);
@@ -331,6 +363,8 @@ int pa__init(pa_module*m) {
ss = m->core->default_sample_spec;
map = m->core->default_channel_map;
+ avoid_processing = m->core->avoid_processing;
+
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;
@@ -347,6 +381,25 @@ int pa__init(pa_module*m) {
goto fail;
}
+ u->dump_fd = -1;
+ if ((dump_file = pa_modargs_get_value(ma, "dump", NULL))) {
+ char dump_path[1024];
+
+ pa_snprintf(dump_path, sizeof(dump_path), "/tmp/pulse-%s", dump_file);
+
+ if ((u->dump_fd = pa_open_cloexec(dump_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) {
+ pa_log_error("Could not open dump file: %s (%s)", dump_path, pa_cstrerror(errno));
+ goto fail;
+ }
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "avoid_processing", &avoid_processing) < 0) {
+ pa_log("Failed to parse avoid_processing argument.");
+ pa_sink_new_data_done(&data);
+ goto fail;
+ }
+ data.avoid_processing = avoid_processing;
+
pa_sink_new_data_init(&data);
data.driver = __FILE__;
data.module = m;
@@ -479,5 +532,8 @@ void pa__done(pa_module*m) {
if (u->formats)
pa_idxset_free(u->formats, (pa_free_cb_t) pa_format_info_free);
+ if (u->dump_fd >= 0)
+ pa_close(u->dump_fd);
+
pa_xfree(u);
}
diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c
index 7a107c44d..72b431d4d 100644
--- a/src/modules/module-udev-detect.c
+++ b/src/modules/module-udev-detect.c
@@ -47,7 +47,8 @@ PA_MODULE_USAGE(
"ignore_dB= "
"deferred_volume= "
"use_ucm= "
- "avoid_resampling=");
+ "avoid_resampling= "
+ "avoid_processing=");
struct device {
char *path;
@@ -70,6 +71,7 @@ struct userdata {
bool deferred_volume:1;
bool use_ucm:1;
bool avoid_resampling:1;
+ bool avoid_processing:1;
uint32_t tsched_buffer_size;
@@ -89,6 +91,7 @@ static const char* const valid_modargs[] = {
"deferred_volume",
"use_ucm",
"avoid_resampling",
+ "avoid_processing",
NULL
};
@@ -415,6 +418,7 @@ static void card_changed(struct userdata *u, struct udev_device *dev) {
"deferred_volume=%s "
"use_ucm=%s "
"avoid_resampling=%s "
+ "avoid_processing=%s "
"card_properties=\"module-udev-detect.discovered=1\"",
path_get_card_id(path),
n,
@@ -424,7 +428,8 @@ static void card_changed(struct userdata *u, struct udev_device *dev) {
pa_yes_no(u->ignore_dB),
pa_yes_no(u->deferred_volume),
pa_yes_no(u->use_ucm),
- pa_yes_no(u->avoid_resampling));
+ pa_yes_no(u->avoid_resampling),
+ pa_yes_no(u->avoid_processing));
pa_xfree(n);
if (u->tsched_buffer_size_valid)
@@ -698,6 +703,7 @@ int pa__init(pa_module *m) {
bool use_tsched = true, fixed_latency_range = false, ignore_dB = false, deferred_volume = m->core->deferred_volume;
bool use_ucm = true;
bool avoid_resampling;
+ bool avoid_processing;
pa_assert(m);
@@ -757,6 +763,13 @@ int pa__init(pa_module *m) {
}
u->avoid_resampling = avoid_resampling;
+ avoid_processing = m->core->avoid_processing;
+ if (pa_modargs_get_value_boolean(ma, "avoid_processing", &avoid_processing) < 0) {
+ pa_log("Failed to parse avoid_processing= argument.");
+ goto fail;
+ }
+ u->avoid_processing = avoid_processing;
+
if (!(u->udev = udev_new())) {
pa_log("Failed to initialize udev library.");
goto fail;
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index 7ac06f5d7..1d45ed112 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -228,6 +228,7 @@ struct pa_core {
bool running_as_daemon:1;
bool realtime_scheduling:1;
bool avoid_resampling:1;
+ bool avoid_processing:1;
bool disable_remixing:1;
bool remixing_use_all_sink_channels:1;
bool remixing_produce_lfe:1;
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index 4380087ca..be2309023 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -493,7 +493,7 @@ int pa_sink_input_new(
module-suspend-on-idle can resume a sink */
pa_log_info("Trying to change sample spec");
- pa_sink_reconfigure(data->sink, &data->sample_spec, pa_sink_input_new_data_is_passthrough(data));
+ pa_sink_reconfigure(data->sink, &data->sample_spec, &data->channel_map, pa_sink_input_new_data_is_passthrough(data));
}
if (pa_sink_input_new_data_is_passthrough(data) &&
@@ -709,7 +709,7 @@ static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state)
!pa_sample_spec_equal(&i->sample_spec, &i->sink->sample_spec)) {
/* We were uncorked and the sink was not playing anything -- let's try
* to update the sample format and rate to avoid resampling */
- pa_sink_reconfigure(i->sink, &i->sample_spec, pa_sink_input_is_passthrough(i));
+ pa_sink_reconfigure(i->sink, &i->sample_spec, &i->channel_map, pa_sink_input_is_passthrough(i));
}
pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0);
@@ -798,9 +798,6 @@ void pa_sink_input_unlink(pa_sink_input *i) {
i->state = PA_SINK_INPUT_UNLINKED;
if (linked && i->sink) {
- if (pa_sink_input_is_passthrough(i))
- pa_sink_leave_passthrough(i->sink);
-
/* We might need to update the sink's volume if we are in flat volume mode. */
if (pa_sink_flat_volume_enabled(i->sink))
pa_sink_set_volume(i->sink, NULL, false, false);
@@ -812,9 +809,15 @@ void pa_sink_input_unlink(pa_sink_input *i) {
reset_callbacks(i);
if (i->sink) {
- if (PA_SINK_IS_LINKED(i->sink->state))
+ if (PA_SINK_IS_LINKED(i->sink->state)) {
pa_sink_update_status(i->sink);
+ if (pa_sink_input_is_passthrough(i)) {
+ pa_log_debug("Leaving passthrough, trying to restore previous configuration");
+ pa_sink_reconfigure(i->sink, NULL, NULL, false);
+ }
+ }
+
i->sink = NULL;
}
@@ -910,9 +913,6 @@ void pa_sink_input_put(pa_sink_input *i) {
set_real_ratio(i, &i->volume);
}
- if (pa_sink_input_is_passthrough(i))
- pa_sink_enter_passthrough(i->sink);
-
i->thread_info.soft_volume = i->soft_volume;
i->thread_info.muted = i->muted;
@@ -1422,6 +1422,11 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool s
pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &i->sample_spec));
pa_assert(i->volume_writable);
+ if (pa_sink_input_is_passthrough(i) && !pa_cvolume_is_norm(volume)) {
+ pa_log_info("Not changing volume for passthrough sink input");
+ return;
+ }
+
if (!absolute && pa_sink_flat_volume_enabled(i->sink)) {
v = i->sink->reference_volume;
pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map);
@@ -1889,9 +1894,6 @@ int pa_sink_input_start_move(pa_sink_input *i) {
if (i->state == PA_SINK_INPUT_CORKED)
pa_assert_se(i->sink->n_corked-- >= 1);
- if (pa_sink_input_is_passthrough(i))
- pa_sink_leave_passthrough(i->sink);
-
if (pa_sink_flat_volume_enabled(i->sink))
/* We might need to update the sink's volume if we are in flat
* volume mode. */
@@ -1901,6 +1903,11 @@ int pa_sink_input_start_move(pa_sink_input *i) {
pa_sink_update_status(i->sink);
+ if (pa_sink_input_is_passthrough(i)) {
+ pa_log_debug("Leaving passthrough, trying to restore previous configuration");
+ pa_sink_reconfigure(i->sink, NULL, NULL, false);
+ }
+
PA_HASHMAP_FOREACH(v, i->volume_factor_sink_items, state)
pa_cvolume_remap(&v->volume, &i->sink->channel_map, &i->channel_map);
@@ -2176,7 +2183,7 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, bool save) {
SINK_INPUT_MOVE_FINISH hook */
pa_log_info("Trying to change sample spec");
- pa_sink_reconfigure(dest, &i->sample_spec, pa_sink_input_is_passthrough(i));
+ pa_sink_reconfigure(dest, &i->sample_spec, &i->channel_map, pa_sink_input_is_passthrough(i));
}
if (i->moving)
@@ -2210,9 +2217,6 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, bool save) {
update_volume_due_to_moving(i, dest);
- if (pa_sink_input_is_passthrough(i))
- pa_sink_enter_passthrough(i->sink);
-
pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0);
/* Reset move variable */
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 0f0dc56fc..0f4fed0a3 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -121,6 +121,13 @@ void pa_sink_new_data_set_avoid_resampling(pa_sink_new_data *data, bool avoid_re
data->avoid_resampling = avoid_resampling;
}
+void pa_sink_new_data_set_avoid_processing(pa_sink_new_data *data, bool avoid_processing) {
+ pa_assert(data);
+
+ data->avoid_processing_is_set = true;
+ data->avoid_processing = avoid_processing;
+}
+
void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume) {
pa_assert(data);
@@ -272,7 +279,9 @@ pa_sink* pa_sink_new(
s->sample_spec = data->sample_spec;
s->channel_map = data->channel_map;
- s->default_sample_rate = s->sample_spec.rate;
+ s->default_sample_spec = s->sample_spec;
+ pa_sample_spec_init(&s->saved_spec);
+ pa_channel_map_init(&s->saved_map);
if (data->alternate_sample_rate_is_set)
s->alternate_sample_rate = data->alternate_sample_rate;
@@ -284,6 +293,11 @@ pa_sink* pa_sink_new(
else
s->avoid_resampling = s->core->avoid_resampling;
+ if (data->avoid_processing_is_set)
+ s->avoid_processing = data->avoid_processing;
+ else
+ s->avoid_processing = s->core->avoid_processing;
+
s->inputs = pa_idxset_new(NULL, NULL);
s->n_corked = 0;
s->input_to_master = NULL;
@@ -376,6 +390,7 @@ pa_sink* pa_sink_new(
pa_source_new_data_set_channel_map(&source_data, &s->channel_map);
pa_source_new_data_set_alternate_sample_rate(&source_data, s->alternate_sample_rate);
pa_source_new_data_set_avoid_resampling(&source_data, s->avoid_resampling);
+ pa_source_new_data_set_avoid_processing(&source_data, s->avoid_processing);
source_data.name = pa_sprintf_malloc("%s.monitor", name);
source_data.driver = data->driver;
source_data.module = data->module;
@@ -1479,30 +1494,48 @@ void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) {
}
/* Called from main thread */
-void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough) {
+void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough) {
pa_sample_spec desired_spec;
- uint32_t default_rate = s->default_sample_rate;
+ pa_sample_format_t default_format = s->default_sample_spec.format;
+ uint32_t default_rate = s->default_sample_spec.rate;
uint32_t alternate_rate = s->alternate_sample_rate;
+ uint8_t default_channels = s->default_sample_spec.channels;
uint32_t idx;
pa_sink_input *i;
bool default_rate_is_usable = false;
bool alternate_rate_is_usable = false;
bool avoid_resampling = s->avoid_resampling;
+ bool avoid_processing = s->avoid_processing;
+ pa_channel_map old_map, *new_map;
+ bool restore = spec == NULL;
- if (pa_sample_spec_equal(spec, &s->sample_spec))
+ /* if spec is unspecified (i.e. we want to restore), map must not be specified either */
+ pa_assert(spec != NULL || map == NULL);
+ pa_assert(spec == NULL || pa_sample_spec_valid(spec));
+ pa_assert(map == NULL || pa_channel_map_valid(map));
+
+ if (!restore && pa_sample_spec_equal(spec, &s->sample_spec))
return;
+ if (restore && !pa_sample_spec_valid(&s->saved_spec)) {
+ /* If we were asked to restore and nothing was saved, this is not an
+ * error -- it means that no reconfiguration was required in the
+ * "entry" phase, so none is required in the "exit" phase either.
+ */
+ return;
+ }
+
if (!s->reconfigure)
return;
- if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !avoid_resampling)) {
+ if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !restore && !avoid_resampling && !avoid_processing)) {
pa_log_debug("Default and alternate sample rates are the same, so there is no point in switching.");
return;
}
if (PA_SINK_IS_RUNNING(s->state)) {
- pa_log_info("Cannot update sample spec, SINK_IS_RUNNING, will keep using %s and %u Hz",
- pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.rate);
+ pa_log_info("Cannot update sample spec, SINK_IS_RUNNING, will keep using %s, %u ch and %u Hz",
+ pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.channels, s->sample_spec.rate);
return;
}
@@ -1513,30 +1546,50 @@ void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough) {
}
}
- if (PA_UNLIKELY(!pa_sample_spec_valid(spec)))
- return;
-
- desired_spec = s->sample_spec;
-
if (passthrough) {
- /* We have to try to use the sink input format and rate */
- desired_spec.format = spec->format;
- desired_spec.rate = spec->rate;
+ /* Save the previous sample spec and channel map, we will try to restore it when leaving passthrough */
+ s->saved_spec = s->sample_spec;
+ s->saved_map = s->channel_map;
+
+ /* Save the volume, we're going to reset it to NORM while in passthrough */
+ s->saved_volume = *pa_sink_get_volume(s, false);
+ s->saved_save_volume = s->save_volume;
+ }
+
+ if (restore) {
+ /* We try to restore the saved spec */
+ desired_spec = s->saved_spec;
+
+ } else if (passthrough) {
+ /* We have to try to use the sink input spec */
+ desired_spec = *spec;
+
+ } else if (avoid_processing) {
+ desired_spec = s->sample_spec;
+
+ if (spec->rate >= default_rate || spec->rate >= alternate_rate)
+ desired_spec.rate = spec->rate;
+ if (spec->channels >= default_channels)
+ desired_spec.channels = spec->channels;
+ if (pa_sample_size_of_format(spec->format) >= pa_sample_size_of_format(default_format))
+ desired_spec.format = spec->format;
} else if (avoid_resampling) {
/* We just try to set the sink input's sample rate if it's not too low */
+ desired_spec = s->sample_spec;
+
if (spec->rate >= default_rate || spec->rate >= alternate_rate)
desired_spec.rate = spec->rate;
- desired_spec.format = spec->format;
} else if (default_rate == spec->rate || alternate_rate == spec->rate) {
/* We can directly try to use this rate */
+ desired_spec = s->sample_spec;
desired_spec.rate = spec->rate;
- }
-
- if (desired_spec.rate != spec->rate) {
+ } else {
/* See if we can pick a rate that results in less resampling effort */
+ desired_spec = s->sample_spec;
+
if (default_rate % 11025 == 0 && spec->rate % 11025 == 0)
default_rate_is_usable = true;
if (default_rate % 4000 == 0 && spec->rate % 4000 == 0)
@@ -1552,26 +1605,85 @@ void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough) {
desired_spec.rate = default_rate;
}
+ /* We don't expect to change only the channel map, so we don't check that.
+ * If the passthrough state is toggled, we want to continue through and
+ * make sure volumes are restored and the sink can toggle passthrough
+ * state if it's keeping track. */
if (pa_sample_spec_equal(&desired_spec, &s->sample_spec) && passthrough == pa_sink_is_passthrough(s))
return;
if (!passthrough && pa_sink_used_by(s) > 0)
return;
- pa_log_debug("Suspending sink %s due to changing format, desired format = %s rate = %u",
- s->name, pa_sample_format_to_string(desired_spec.format), desired_spec.rate);
+ pa_log_debug("Suspending sink %s due to changing format, desired format = %s rate = %u, channels = %u",
+ s->name, pa_sample_format_to_string(desired_spec.format), desired_spec.rate, desired_spec.channels);
pa_sink_suspend(s, true, PA_SUSPEND_INTERNAL);
- s->reconfigure(s, &desired_spec, passthrough);
+ /* Keep the old channel map in case it changes */
+ old_map = s->channel_map;
- /* update monitor source as well */
- if (s->monitor_source && !passthrough)
- pa_source_reconfigure(s->monitor_source, &s->sample_spec, false);
- pa_log_info("Reconfigured successfully");
+ if (restore) {
+ /* Restore the previous channel map as well */
+ new_map = &s->saved_map;
+ } else {
+ /* Set the requested channel map */
+ new_map = map;
+ }
- PA_IDXSET_FOREACH(i, s->inputs, idx) {
- if (i->state == PA_SINK_INPUT_CORKED)
- pa_sink_input_update_resampler(i, true);
+ if (s->reconfigure(s, &desired_spec, new_map, passthrough) >= 0) {
+ char spec_str[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ char map_str[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ /* update monitor source as well */
+ if (s->monitor_source && !passthrough)
+ pa_source_reconfigure(s->monitor_source, &s->sample_spec, &s->channel_map, false);
+
+ pa_log_info("Reconfigured successfully to: %s, %s",
+ pa_sample_spec_snprint(spec_str, sizeof(spec_str), &s->sample_spec),
+ pa_channel_map_snprint(map_str, sizeof(map_str), &s->channel_map));
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ if (i->state == PA_SINK_INPUT_CORKED)
+ pa_sink_input_update_resampler(i, true);
+ }
+ }
+
+ if (!restore && !pa_channel_map_equal(&old_map, &s->channel_map)) {
+ /* Remap stored volumes to the new channel map if we're not just restoring a previously saved volume */
+ pa_cvolume_remap(&s->reference_volume, &old_map, &s->channel_map);
+ pa_cvolume_remap(&s->real_volume, &old_map, &s->channel_map);
+ pa_cvolume_remap(&s->soft_volume, &old_map, &s->channel_map);
+ }
+
+ if (passthrough) {
+ /* set the volume to NORM */
+ pa_cvolume volume;
+
+ pa_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM));
+ pa_sink_set_volume(s, &volume, true, false);
+
+ /* disable the monitor in passthrough mode */
+ if (s->monitor_source) {
+ pa_log_debug("Suspending monitor source %s, because the sink is entering the passthrough mode.", s->monitor_source->name);
+ pa_source_suspend(s->monitor_source, true, PA_SUSPEND_PASSTHROUGH);
+ }
+ }
+
+ if (restore) {
+ /* Reset saved spec and channel map to bail early if we inadvertently
+ * use them (which is not expected after this) */
+ pa_sample_spec_init(&s->saved_spec);
+ pa_channel_map_init(&s->saved_map);
+
+ /* Restore sink volume to what it was before we entered passthrough mode */
+ pa_sink_set_volume(s, &s->saved_volume, true, s->saved_save_volume);
+ pa_cvolume_init(&s->saved_volume);
+ s->saved_save_volume = false;
+
+ if (s->monitor_source) {
+ pa_log_debug("Resuming monitor source %s, because the sink is leaving the passthrough mode.", s->monitor_source->name);
+ pa_source_suspend(s->monitor_source, false, PA_SUSPEND_PASSTHROUGH);
+ }
}
pa_sink_suspend(s, false, PA_SUSPEND_INTERNAL);
@@ -1727,46 +1839,6 @@ bool pa_sink_is_passthrough(pa_sink *s) {
return false;
}
-/* Called from main context */
-void pa_sink_enter_passthrough(pa_sink *s) {
- pa_cvolume volume;
-
- /* The sink implementation is reconfigured for passthrough in
- * pa_sink_reconfigure(). This function sets the PA core objects to
- * passthrough mode. */
-
- /* disable the monitor in passthrough mode */
- if (s->monitor_source) {
- pa_log_debug("Suspending monitor source %s, because the sink is entering the passthrough mode.", s->monitor_source->name);
- pa_source_suspend(s->monitor_source, true, PA_SUSPEND_PASSTHROUGH);
- }
-
- /* set the volume to NORM */
- s->saved_volume = *pa_sink_get_volume(s, true);
- s->saved_save_volume = s->save_volume;
-
- pa_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM));
- pa_sink_set_volume(s, &volume, true, false);
-
- pa_log_debug("Suspending/Restarting sink %s to enter passthrough mode", s->name);
-}
-
-/* Called from main context */
-void pa_sink_leave_passthrough(pa_sink *s) {
- /* Unsuspend monitor */
- if (s->monitor_source) {
- pa_log_debug("Resuming monitor source %s, because the sink is leaving the passthrough mode.", s->monitor_source->name);
- pa_source_suspend(s->monitor_source, false, PA_SUSPEND_PASSTHROUGH);
- }
-
- /* Restore sink volume to what it was before we entered passthrough mode */
- pa_sink_set_volume(s, &s->saved_volume, true, s->saved_save_volume);
-
- pa_cvolume_init(&s->saved_volume);
- s->saved_save_volume = false;
-
-}
-
/* Called from main context. */
static void compute_reference_ratio(pa_sink_input *i) {
unsigned c = 0;
@@ -3980,40 +4052,41 @@ done:
}
/* Called from the main thread */
-void pa_sink_set_sample_format(pa_sink *s, pa_sample_format_t format) {
- pa_sample_format_t old_format;
+void pa_sink_set_sample_spec(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map) {
+ pa_sample_spec old_spec;
+ pa_channel_map old_map;
+ char spec_str[PA_SAMPLE_SPEC_SNPRINT_MAX], old_spec_str[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ char old_map_str[PA_CHANNEL_MAP_SNPRINT_MAX], new_map_str[PA_CHANNEL_MAP_SNPRINT_MAX];
+ bool changed = false;
pa_assert(s);
- pa_assert(pa_sample_format_valid(format));
+ pa_assert(pa_sample_spec_valid(spec));
+ pa_assert(map);
+ pa_assert(pa_channel_map_valid(map));
- old_format = s->sample_spec.format;
- if (old_format == format)
- return;
+ old_spec = s->sample_spec;
+ if (!pa_sample_spec_equal(&old_spec, spec)) {
+ pa_sample_spec_snprint(spec_str, sizeof(spec_str), spec);
+ pa_sample_spec_snprint(old_spec_str, sizeof(old_spec_str), &old_spec);
- pa_log_info("%s: format: %s -> %s",
- s->name, pa_sample_format_to_string(old_format), pa_sample_format_to_string(format));
+ pa_log_info("%s: spec: %s -> %s", s->name, old_spec_str, spec_str);
- s->sample_spec.format = format;
+ s->sample_spec = *spec;
+ changed = true;
+ }
- pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
-}
+ old_map = s->channel_map;
+ if (!pa_channel_map_equal(&old_map, map)) {
+ pa_log_info("%s: channel map: %s -> %s", s->name,
+ pa_channel_map_snprint(old_map_str, sizeof(old_map_str), &old_map),
+ pa_channel_map_snprint(new_map_str, sizeof(new_map_str), map));
-/* Called from the main thread */
-void pa_sink_set_sample_rate(pa_sink *s, uint32_t rate) {
- uint32_t old_rate;
+ s->channel_map = *map;
+ changed = true;
+ }
- pa_assert(s);
- pa_assert(pa_sample_rate_valid(rate));
-
- old_rate = s->sample_spec.rate;
- if (old_rate == rate)
- return;
-
- pa_log_info("%s: rate: %u -> %u", s->name, old_rate, rate);
-
- s->sample_spec.rate = rate;
-
- pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ if (changed)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
}
/* Called from the main thread. */
@@ -4032,7 +4105,8 @@ void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume) {
s->reference_volume = *volume;
pa_log_debug("The reference volume of sink %s changed from %s to %s.", s->name,
- pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &s->channel_map,
+ /* we don't print old volume channel map as it might have changed */
+ pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, NULL,
s->flags & PA_SINK_DECIBEL_VOLUME),
pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &s->channel_map,
s->flags & PA_SINK_DECIBEL_VOLUME));
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index 383edacb5..15bfafd08 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -81,9 +81,10 @@ struct pa_sink {
pa_sample_spec sample_spec;
pa_channel_map channel_map;
- uint32_t default_sample_rate;
+ pa_sample_spec default_sample_spec;
uint32_t alternate_sample_rate;
bool avoid_resampling:1;
+ bool avoid_processing:1;
pa_idxset *inputs;
unsigned n_corked;
@@ -107,7 +108,9 @@ struct pa_sink {
bool save_muted:1;
bool port_changing:1;
- /* Saved volume state while we're in passthrough mode */
+ /* Saved state while we're in passthrough mode */
+ pa_sample_spec saved_spec;
+ pa_channel_map saved_map;
pa_cvolume saved_volume;
bool saved_save_volume:1;
@@ -268,7 +271,7 @@ struct pa_sink {
/* Called whenever device parameters need to be changed. Called from
* main thread. */
- void (*reconfigure)(pa_sink *s, pa_sample_spec *spec, bool passthrough);
+ int (*reconfigure)(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough);
/* Contains copies of the above data so that the real-time worker
* thread can work without access locking */
@@ -383,6 +386,7 @@ typedef struct pa_sink_new_data {
pa_channel_map channel_map;
uint32_t alternate_sample_rate;
bool avoid_resampling:1;
+ bool avoid_processing:1;
pa_cvolume volume;
bool muted:1;
@@ -390,6 +394,7 @@ typedef struct pa_sink_new_data {
bool channel_map_is_set:1;
bool alternate_sample_rate_is_set:1;
bool avoid_resampling_is_set:1;
+ bool avoid_processing_is_set:1;
bool volume_is_set:1;
bool muted_is_set:1;
@@ -406,6 +411,7 @@ void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_sp
void pa_sink_new_data_set_channel_map(pa_sink_new_data *data, const pa_channel_map *map);
void pa_sink_new_data_set_alternate_sample_rate(pa_sink_new_data *data, const uint32_t alternate_sample_rate);
void pa_sink_new_data_set_avoid_resampling(pa_sink_new_data *data, bool avoid_resampling);
+void pa_sink_new_data_set_avoid_processing(pa_sink_new_data *data, bool avoid_processing);
void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume);
void pa_sink_new_data_set_muted(pa_sink_new_data *data, bool mute);
void pa_sink_new_data_set_port(pa_sink_new_data *data, const char *port);
@@ -450,7 +456,7 @@ unsigned pa_device_init_priority(pa_proplist *p);
/**** May be called by everyone, from main context */
-void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough);
+void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough);
void pa_sink_set_port_latency_offset(pa_sink *s, int64_t offset);
/* The returned value is supposed to be in the time domain of the sound card! */
@@ -482,9 +488,6 @@ bool pa_sink_is_filter(pa_sink *s);
/* Is the sink in passthrough mode? (that is, is there a passthrough sink input
* connected to this sink? */
bool pa_sink_is_passthrough(pa_sink *s);
-/* These should be called when a sink enters/leaves passthrough mode */
-void pa_sink_enter_passthrough(pa_sink *s);
-void pa_sink_leave_passthrough(pa_sink *s);
void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, bool sendmsg, bool save);
const pa_cvolume *pa_sink_get_volume(pa_sink *sink, bool force_refresh);
@@ -524,8 +527,7 @@ bool pa_sink_set_formats(pa_sink *s, pa_idxset *formats);
bool pa_sink_check_format(pa_sink *s, pa_format_info *f);
pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats);
-void pa_sink_set_sample_format(pa_sink *s, pa_sample_format_t format);
-void pa_sink_set_sample_rate(pa_sink *s, uint32_t rate);
+void pa_sink_set_sample_spec(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map);
/*** To be called exclusively by the sink driver, from IO context */
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
index 2e2b7a274..221c05598 100644
--- a/src/pulsecore/source-output.c
+++ b/src/pulsecore/source-output.c
@@ -380,7 +380,8 @@ int pa_source_output_new(
module-suspend-on-idle can resume a source */
pa_log_info("Trying to change sample spec");
- pa_source_reconfigure(data->source, &data->sample_spec, pa_source_output_new_data_is_passthrough(data));
+ pa_source_reconfigure(data->source, &data->sample_spec, &data->channel_map,
+ pa_source_output_new_data_is_passthrough(data));
}
if (pa_source_output_new_data_is_passthrough(data) &&
@@ -559,7 +560,7 @@ static void source_output_set_state(pa_source_output *o, pa_source_output_state_
!pa_sample_spec_equal(&o->sample_spec, &o->source->sample_spec)) {
/* We were uncorked and the source was not playing anything -- let's try
* to update the sample format and rate to avoid resampling */
- pa_source_reconfigure(o->source, &o->sample_spec, pa_source_output_is_passthrough(o));
+ pa_source_reconfigure(o->source, &o->sample_spec, &o->channel_map, pa_source_output_is_passthrough(o));
}
pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0);
@@ -614,9 +615,6 @@ void pa_source_output_unlink(pa_source_output*o) {
o->state = PA_SOURCE_OUTPUT_UNLINKED;
if (linked && o->source) {
- if (pa_source_output_is_passthrough(o))
- pa_source_leave_passthrough(o->source);
-
/* We might need to update the source's volume if we are in flat volume mode. */
if (pa_source_flat_volume_enabled(o->source))
pa_source_set_volume(o->source, NULL, false, false);
@@ -628,9 +626,15 @@ void pa_source_output_unlink(pa_source_output*o) {
reset_callbacks(o);
if (o->source) {
- if (PA_SOURCE_IS_LINKED(o->source->state))
+ if (PA_SOURCE_IS_LINKED(o->source->state)) {
pa_source_update_status(o->source);
+ if (pa_source_output_is_passthrough(o)) {
+ pa_log_debug("Leaving passthrough, trying to restore previous configuration");
+ pa_source_reconfigure(o->source, NULL, NULL, false);
+ }
+ }
+
o->source = NULL;
}
@@ -705,9 +709,6 @@ void pa_source_output_put(pa_source_output *o) {
set_real_ratio(o, &o->volume);
}
- if (pa_source_output_is_passthrough(o))
- pa_source_enter_passthrough(o->source);
-
o->thread_info.soft_volume = o->soft_volume;
o->thread_info.muted = o->muted;
@@ -1005,6 +1006,11 @@ void pa_source_output_set_volume(pa_source_output *o, const pa_cvolume *volume,
pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &o->sample_spec));
pa_assert(o->volume_writable);
+ if (pa_source_output_is_passthrough(o) && !pa_cvolume_is_norm(volume)) {
+ pa_log_info("Not changing volume for passthrough source output");
+ return;
+ }
+
if (!absolute && pa_source_flat_volume_enabled(o->source)) {
v = o->source->reference_volume;
pa_cvolume_remap(&v, &o->source->channel_map, &o->channel_map);
@@ -1400,9 +1406,6 @@ int pa_source_output_start_move(pa_source_output *o) {
if (o->state == PA_SOURCE_OUTPUT_CORKED)
pa_assert_se(origin->n_corked-- >= 1);
- if (pa_source_output_is_passthrough(o))
- pa_source_leave_passthrough(o->source);
-
if (pa_source_flat_volume_enabled(o->source))
/* We might need to update the source's volume if we are in flat
* volume mode. */
@@ -1412,6 +1415,11 @@ int pa_source_output_start_move(pa_source_output *o) {
pa_source_update_status(o->source);
+ if (pa_source_output_is_passthrough(o)) {
+ pa_log_debug("Leaving passthrough, trying to restore previous configuration");
+ pa_source_reconfigure(o->source, NULL, NULL, false);
+ }
+
pa_cvolume_remap(&o->volume_factor_source, &o->source->channel_map, &o->channel_map);
o->source = NULL;
@@ -1605,7 +1613,7 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, bool save
SOURCE_OUTPUT_MOVE_FINISH hook */
pa_log_info("Trying to change sample spec");
- pa_source_reconfigure(dest, &o->sample_spec, pa_source_output_is_passthrough(o));
+ pa_source_reconfigure(dest, &o->sample_spec, &o->channel_map, pa_source_output_is_passthrough(o));
}
if (o->moving)
@@ -1634,9 +1642,6 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, bool save
update_volume_due_to_moving(o, dest);
- if (pa_source_output_is_passthrough(o))
- pa_source_enter_passthrough(o->source);
-
pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0);
pa_log_debug("Successfully moved source output %i to %s.", o->index, dest->name);
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 99d8dde6e..c1fc6f459 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -111,6 +111,13 @@ void pa_source_new_data_set_avoid_resampling(pa_source_new_data *data, bool avoi
data->avoid_resampling = avoid_resampling;
}
+void pa_source_new_data_set_avoid_processing(pa_source_new_data *data, bool avoid_processing) {
+ pa_assert(data);
+
+ data->avoid_processing_is_set = true;
+ data->avoid_processing = avoid_processing;
+}
+
void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *volume) {
pa_assert(data);
@@ -258,7 +265,9 @@ pa_source* pa_source_new(
s->sample_spec = data->sample_spec;
s->channel_map = data->channel_map;
- s->default_sample_rate = s->sample_spec.rate;
+ s->default_sample_spec = s->sample_spec;
+ pa_sample_spec_init(&s->saved_spec);
+ pa_channel_map_init(&s->saved_map);
if (data->alternate_sample_rate_is_set)
s->alternate_sample_rate = data->alternate_sample_rate;
@@ -270,6 +279,11 @@ pa_source* pa_source_new(
else
s->avoid_resampling = s->core->avoid_resampling;
+ if (data->avoid_processing_is_set)
+ s->avoid_processing = data->avoid_processing;
+ else
+ s->avoid_processing = s->core->avoid_processing;
+
s->outputs = pa_idxset_new(NULL, NULL);
s->n_corked = 0;
s->monitor_of = NULL;
@@ -1045,64 +1059,108 @@ void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *
}
/* Called from main thread */
-void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough) {
- uint32_t idx;
- pa_source_output *o;
+void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough) {
+ int ret;
pa_sample_spec desired_spec;
- uint32_t default_rate = s->default_sample_rate;
+ pa_sample_format_t default_format = s->default_sample_spec.format;
+ uint32_t default_rate = s->default_sample_spec.rate;
uint32_t alternate_rate = s->alternate_sample_rate;
+ uint8_t default_channels = s->default_sample_spec.channels;
bool default_rate_is_usable = false;
bool alternate_rate_is_usable = false;
bool avoid_resampling = s->avoid_resampling;
+ bool avoid_processing = s->avoid_processing;
+ pa_channel_map old_map, *new_map;
+ bool restore = spec == NULL;
- if (pa_sample_spec_equal(spec, &s->sample_spec))
+ /* if spec is unspecified (i.e. we want to restore), map must not be specified either */
+ pa_assert(spec != NULL || map == NULL);
+ pa_assert(spec == NULL || pa_sample_spec_valid(spec));
+ pa_assert(map == NULL || pa_channel_map_valid(map));
+
+
+ if (!restore && pa_sample_spec_equal(spec, &s->sample_spec))
return;
+ if (restore && !pa_sample_spec_valid(&s->saved_spec)) {
+ /* If we were asked to restore and nothing was saved, this is not an
+ * error -- it means that no reconfiguration was required in the
+ * "entry" phase, so none is required in the "exit" phase either.
+ */
+ return;
+ }
+
if (!s->reconfigure && !s->monitor_of)
return;
- if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !avoid_resampling)) {
+ if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !restore && !avoid_resampling && !avoid_processing)) {
pa_log_debug("Default and alternate sample rates are the same, so there is no point in switching.");
return;
}
if (PA_SOURCE_IS_RUNNING(s->state)) {
- pa_log_info("Cannot update sample spec, SOURCE_IS_RUNNING, will keep using %s and %u Hz",
- pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.rate);
+ pa_log_info("Cannot update sample spec, SOURCE_IS_RUNNING, will keep using %s, %u ch and %u Hz",
+ pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.channels, s->sample_spec.rate);
return;
}
if (s->monitor_of) {
+ /* We only allow monitor source reconfiguration to match its sink */
+ if (!pa_sample_spec_equal(spec, &s->monitor_of->sample_spec) ||
+ !pa_channel_map_equal(map, &s->monitor_of->channel_map)) {
+ pa_log_info("Skipping monitor source reconfigruation to different spec from sink.");
+ return;
+ }
if (PA_SINK_IS_RUNNING(s->monitor_of->state)) {
pa_log_info("Cannot update sample spec, this is a monitor source and the sink is running.");
return;
}
}
- if (PA_UNLIKELY(!pa_sample_spec_valid(spec)))
- return;
-
- desired_spec = s->sample_spec;
-
if (passthrough) {
- /* We have to try to use the source output format and rate */
- desired_spec.format = spec->format;
- desired_spec.rate = spec->rate;
+ /* Save the previous sample spec and channel map, we will try to restore it when leaving passthrough */
+ s->saved_spec = s->sample_spec;
+ s->saved_map = s->channel_map;
+
+ /* Save the volume, we're going to reset it to NORM while in passthrough */
+ s->saved_volume = *pa_source_get_volume(s, false);
+ s->saved_save_volume = s->save_volume;
+ }
+
+ if (restore) {
+ /* We try to restore the saved spec */
+ desired_spec = s->saved_spec;
+
+ } else if (passthrough) {
+ /* We have to try to use the source output spec */
+ desired_spec = *spec;
+
+ } else if (avoid_processing) {
+ desired_spec = s->sample_spec;
+
+ if (spec->rate >= default_rate || spec->rate >= alternate_rate)
+ desired_spec.rate = spec->rate;
+ if (spec->channels >= default_channels)
+ desired_spec.channels = spec->channels;
+ if (pa_sample_size_of_format(spec->format) >= pa_sample_size_of_format(default_format))
+ desired_spec.format = spec->format;
} else if (avoid_resampling) {
/* We just try to set the source output's sample rate if it's not too low */
+ desired_spec = s->sample_spec;
+
if (spec->rate >= default_rate || spec->rate >= alternate_rate)
desired_spec.rate = spec->rate;
- desired_spec.format = spec->format;
} else if (default_rate == spec->rate || alternate_rate == spec->rate) {
/* We can directly try to use this rate */
+ desired_spec = s->sample_spec;
desired_spec.rate = spec->rate;
- }
-
- if (desired_spec.rate != spec->rate) {
+ } else {
/* See if we can pick a rate that results in less resampling effort */
+ desired_spec = s->sample_spec;
+
if (default_rate % 11025 == 0 && spec->rate % 11025 == 0)
default_rate_is_usable = true;
if (default_rate % 4000 == 0 && spec->rate % 4000 == 0)
@@ -1118,39 +1176,89 @@ void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough)
desired_spec.rate = default_rate;
}
+ /* We don't expect to change only the channel map, so we don't check that.
+ * If the passthrough state is toggled, we want to continue through and
+ * make sure volumes are restored and the source can toggle passthrough
+ * state if it's keeping track. */
if (pa_sample_spec_equal(&desired_spec, &s->sample_spec) && passthrough == pa_source_is_passthrough(s))
return;
if (!passthrough && pa_source_used_by(s) > 0)
return;
- pa_log_debug("Suspending source %s due to changing format, desired format = %s rate = %u",
- s->name, pa_sample_format_to_string(desired_spec.format), desired_spec.rate);
+ pa_log_debug("Suspending source %s due to changing format, desired format = %s rate = %u, channels = %u",
+ s->name, pa_sample_format_to_string(desired_spec.format), desired_spec.rate, desired_spec.channels);
+
pa_source_suspend(s, true, PA_SUSPEND_INTERNAL);
- if (s->reconfigure)
- s->reconfigure(s, &desired_spec, passthrough);
- else {
- /* This is a monitor source. */
+ /* Keep the old channel map in case it changes */
+ old_map = s->channel_map;
- /* XXX: This code is written with non-passthrough streams in mind. I
- * have no idea whether the behaviour with passthrough streams is
- * sensible. */
- if (!passthrough) {
- s->sample_spec = desired_spec;
- pa_sink_reconfigure(s->monitor_of, &desired_spec, false);
- s->sample_spec = s->monitor_of->sample_spec;
- } else
- goto unsuspend;
+ if (restore) {
+ /* Restore the previous channel map as well */
+ new_map = &s->saved_map;
+ } else {
+ /* Set the requested channel map */
+ new_map = map;
}
- PA_IDXSET_FOREACH(o, s->outputs, idx) {
- if (o->state == PA_SOURCE_OUTPUT_CORKED)
- pa_source_output_update_resampler(o);
+ if (s->reconfigure)
+ ret = s->reconfigure(s, &desired_spec, new_map, passthrough);
+ else if (s->monitor_of) {
+ /* This is a monitor source, just set the desired spec and map */
+ s->sample_spec = desired_spec;
+ s->channel_map = *new_map;
+ ret = 0;
+ } else {
+ ret = -1;
+ goto unsuspend;
+ }
+
+ if (ret >= 0) {
+ uint32_t idx;
+ pa_source_output *o;
+ char spec_str[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ char map_str[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ pa_log_info("Reconfigured successfully to: %s, %s",
+ pa_sample_spec_snprint(spec_str, sizeof(spec_str), &s->sample_spec),
+ pa_channel_map_snprint(map_str, sizeof(map_str), &s->channel_map));
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ if (o->state == PA_SOURCE_OUTPUT_CORKED)
+ pa_source_output_update_resampler(o);
+ }
+ }
+
+ if (!restore && !pa_channel_map_equal(&old_map, &s->channel_map)) {
+ /* Remap stored volumes to the new channel map if we're not just restoring a previously saved volume */
+ pa_cvolume_remap(&s->reference_volume, &old_map, &s->channel_map);
+ pa_cvolume_remap(&s->real_volume, &old_map, &s->channel_map);
+ pa_cvolume_remap(&s->soft_volume, &old_map, &s->channel_map);
}
pa_log_info("Reconfigured successfully");
+ if (passthrough) {
+ /* set the volume to NORM */
+ pa_cvolume volume;
+
+ pa_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM));
+ pa_source_set_volume(s, &volume, true, false);
+ }
+
+ if (restore) {
+ /* Reset saved spec and channel map to bail early if we inadvertently
+ * use them (which is not expected after this) */
+ pa_sample_spec_init(&s->saved_spec);
+ pa_channel_map_init(&s->saved_map);
+
+ /* Restore source volume to what it was before we entered passthrough mode */
+ pa_source_set_volume(s, &s->saved_volume, true, s->saved_save_volume);
+ pa_cvolume_init(&s->saved_volume);
+ s->saved_save_volume = false;
+ }
+
unsuspend:
pa_source_suspend(s, false, PA_SUSPEND_INTERNAL);
}
@@ -1260,27 +1368,6 @@ bool pa_source_is_passthrough(pa_source *s) {
return (s->monitor_of && pa_sink_is_passthrough(s->monitor_of));
}
-/* Called from main context */
-void pa_source_enter_passthrough(pa_source *s) {
- pa_cvolume volume;
-
- /* set the volume to NORM */
- s->saved_volume = *pa_source_get_volume(s, true);
- s->saved_save_volume = s->save_volume;
-
- pa_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM));
- pa_source_set_volume(s, &volume, true, false);
-}
-
-/* Called from main context */
-void pa_source_leave_passthrough(pa_source *s) {
- /* Restore source volume to what it was before we entered passthrough mode */
- pa_source_set_volume(s, &s->saved_volume, true, s->saved_save_volume);
-
- pa_cvolume_init(&s->saved_volume);
- s->saved_save_volume = false;
-}
-
/* Called from main context. */
static void compute_reference_ratio(pa_source_output *o) {
unsigned c = 0;
@@ -2949,40 +3036,41 @@ done:
}
/* Called from the main thread */
-void pa_source_set_sample_format(pa_source *s, pa_sample_format_t format) {
- pa_sample_format_t old_format;
+void pa_source_set_sample_spec(pa_source *s, pa_sample_spec *spec, pa_channel_map *map) {
+ pa_sample_spec old_spec;
+ pa_channel_map old_map;
+ char spec_str[PA_SAMPLE_SPEC_SNPRINT_MAX], old_spec_str[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ char old_map_str[PA_CHANNEL_MAP_SNPRINT_MAX], new_map_str[PA_CHANNEL_MAP_SNPRINT_MAX];
+ bool changed = false;
pa_assert(s);
- pa_assert(pa_sample_format_valid(format));
+ pa_assert(pa_sample_spec_valid(spec));
+ pa_assert(map);
+ pa_assert(pa_channel_map_valid(map));
- old_format = s->sample_spec.format;
- if (old_format == format)
- return;
+ old_spec = s->sample_spec;
+ if (!pa_sample_spec_equal(&old_spec, spec)) {
+ pa_sample_spec_snprint(spec_str, sizeof(spec_str), spec);
+ pa_sample_spec_snprint(old_spec_str, sizeof(old_spec_str), &old_spec);
- pa_log_info("%s: format: %s -> %s",
- s->name, pa_sample_format_to_string(old_format), pa_sample_format_to_string(format));
+ pa_log_info("%s: spec: %s -> %s", s->name, old_spec_str, spec_str);
- s->sample_spec.format = format;
+ s->sample_spec = *spec;
+ changed = true;
+ }
- pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
-}
+ old_map = s->channel_map;
+ if (!pa_channel_map_equal(&old_map, map)) {
+ pa_log_info("%s: channel map: %s -> %s", s->name,
+ pa_channel_map_snprint(old_map_str, sizeof(old_map_str), &old_map),
+ pa_channel_map_snprint(new_map_str, sizeof(new_map_str), map));
-/* Called from the main thread */
-void pa_source_set_sample_rate(pa_source *s, uint32_t rate) {
- uint32_t old_rate;
+ s->channel_map = *map;
+ changed = true;
+ }
- pa_assert(s);
- pa_assert(pa_sample_rate_valid(rate));
-
- old_rate = s->sample_spec.rate;
- if (old_rate == rate)
- return;
-
- pa_log_info("%s: rate: %u -> %u", s->name, old_rate, rate);
-
- s->sample_spec.rate = rate;
-
- pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ if (changed)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
}
/* Called from the main thread. */
@@ -3001,7 +3089,8 @@ void pa_source_set_reference_volume_direct(pa_source *s, const pa_cvolume *volum
s->reference_volume = *volume;
pa_log_debug("The reference volume of source %s changed from %s to %s.", s->name,
- pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &s->channel_map,
+ /* we don't print old volume channel map as it might have changed */
+ pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, NULL,
s->flags & PA_SOURCE_DECIBEL_VOLUME),
pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &s->channel_map,
s->flags & PA_SOURCE_DECIBEL_VOLUME));
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index aa71ee829..6c39b4e02 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -82,9 +82,10 @@ struct pa_source {
pa_sample_spec sample_spec;
pa_channel_map channel_map;
- uint32_t default_sample_rate;
+ pa_sample_spec default_sample_spec;
uint32_t alternate_sample_rate;
bool avoid_resampling:1;
+ bool avoid_processing:1;
pa_idxset *outputs;
unsigned n_corked;
@@ -108,7 +109,9 @@ struct pa_source {
bool save_muted:1;
bool port_changing:1;
- /* Saved volume state while we're in passthrough mode */
+ /* Saved state while we're in passthrough mode */
+ pa_sample_spec saved_spec;
+ pa_channel_map saved_map;
pa_cvolume saved_volume;
bool saved_save_volume:1;
@@ -226,7 +229,7 @@ struct pa_source {
/* Called whenever device parameters need to be changed. Called from
* main thread. */
- void (*reconfigure)(pa_source *s, pa_sample_spec *spec, bool passthrough);
+ int (*reconfigure)(pa_source *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough);
/* Contains copies of the above data so that the real-time worker
* thread can work without access locking */
@@ -317,6 +320,7 @@ typedef struct pa_source_new_data {
pa_channel_map channel_map;
uint32_t alternate_sample_rate;
bool avoid_resampling:1;
+ bool avoid_processing:1;
pa_cvolume volume;
bool muted:1;
@@ -326,6 +330,7 @@ typedef struct pa_source_new_data {
bool channel_map_is_set:1;
bool alternate_sample_rate_is_set:1;
bool avoid_resampling_is_set:1;
+ bool avoid_processing_is_set:1;
bool namereg_fail:1;
@@ -340,6 +345,7 @@ void pa_source_new_data_set_sample_spec(pa_source_new_data *data, const pa_sampl
void pa_source_new_data_set_channel_map(pa_source_new_data *data, const pa_channel_map *map);
void pa_source_new_data_set_alternate_sample_rate(pa_source_new_data *data, const uint32_t alternate_sample_rate);
void pa_source_new_data_set_avoid_resampling(pa_source_new_data *data, bool avoid_resampling);
+void pa_source_new_data_set_avoid_processing(pa_source_new_data *data, bool avoid_processing);
void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *volume);
void pa_source_new_data_set_muted(pa_source_new_data *data, bool mute);
void pa_source_new_data_set_port(pa_source_new_data *data, const char *port);
@@ -405,9 +411,6 @@ bool pa_source_is_filter(pa_source *s);
/* Is the source in passthrough mode? (that is, is this a monitor source for a sink
* that has a passthrough sink input connected to it. */
bool pa_source_is_passthrough(pa_source *s);
-/* These should be called when a source enters/leaves passthrough mode */
-void pa_source_enter_passthrough(pa_source *s);
-void pa_source_leave_passthrough(pa_source *s);
void pa_source_set_volume(pa_source *source, const pa_cvolume *volume, bool sendmsg, bool save);
const pa_cvolume *pa_source_get_volume(pa_source *source, bool force_refresh);
@@ -419,7 +422,7 @@ bool pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist
int pa_source_set_port(pa_source *s, const char *name, bool save);
-void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough);
+void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough);
unsigned pa_source_linked_by(pa_source *s); /* Number of connected streams */
unsigned pa_source_used_by(pa_source *s); /* Number of connected streams that are not corked */
@@ -445,8 +448,7 @@ pa_idxset* pa_source_get_formats(pa_source *s);
bool pa_source_check_format(pa_source *s, pa_format_info *f);
pa_idxset* pa_source_check_formats(pa_source *s, pa_idxset *in_formats);
-void pa_source_set_sample_format(pa_source *s, pa_sample_format_t format);
-void pa_source_set_sample_rate(pa_source *s, uint32_t rate);
+void pa_source_set_sample_spec(pa_source *s, pa_sample_spec *spec, pa_channel_map *map);
/*** To be called exclusively by the source driver, from IO context */
diff --git a/src/tests/extended-test.c b/src/tests/extended-test.c
index 33c08eef4..27d771962 100644
--- a/src/tests/extended-test.c
+++ b/src/tests/extended-test.c
@@ -40,7 +40,7 @@
static pa_context *context = NULL;
static pa_stream *streams[NSTREAMS];
static pa_mainloop_api *mainloop_api = NULL;
-static const char *bname;
+static const char *binary_name;
static float data[SAMPLE_HZ]; /* one second space */
@@ -172,7 +172,7 @@ START_TEST (extended_test) {
mainloop_api = pa_mainloop_get_api(m);
- context = pa_context_new(mainloop_api, bname);
+ context = pa_context_new(mainloop_api, binary_name);
fail_unless(context != NULL);
pa_context_set_state_callback(context, context_state_callback, NULL);
@@ -205,7 +205,7 @@ int main(int argc, char *argv[]) {
TCase *tc;
SRunner *sr;
- bname = argv[0];
+ binary_name = argv[0];
s = suite_create("Extended");
tc = tcase_create("extended");
diff --git a/src/tests/meson.build b/src/tests/meson.build
index bbdd23130..8318cf160 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -239,7 +239,9 @@ if get_option('daemon')
daemon_tests = [
[ 'extended-test', 'extended-test.c',
[ check_dep, libm_dep, libpulse_dep ] ],
- [ 'passthrough-test', 'passthrough-test.c',
+ [ 'passthrough-test', [ 'passthrough-test.c', 'test-util.c' ],
+ [ check_dep, libpulse_dep, libpulsecommon_dep ] ],
+ [ 'reconfigure-test', [ 'reconfigure-test.c', 'test-util.c' ],
[ check_dep, libpulse_dep, libpulsecommon_dep ] ],
[ 'sync-playback', 'sync-playback.c',
[ check_dep, libm_dep, libpulse_dep ] ],
diff --git a/src/tests/passthrough-test.c b/src/tests/passthrough-test.c
index cbeedd03a..ff1268498 100644
--- a/src/tests/passthrough-test.c
+++ b/src/tests/passthrough-test.c
@@ -19,234 +19,62 @@
#include
#endif
-#include
-#include
-
#include
#include
-#include
+#include "test-util.h"
#define SINK_NAME "passthrough-test"
#define RATE 48000
#define CHANNELS 6
-#define WAIT_FOR_OPERATION(o) \
- do { \
- while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { \
- pa_threaded_mainloop_wait(mainloop); \
- } \
- \
- fail_unless(pa_operation_get_state(o) == PA_OPERATION_DONE); \
- pa_operation_unref(o); \
- } while (false)
-
-static pa_threaded_mainloop *mainloop = NULL;
-static pa_context *context = NULL;
-static pa_mainloop_api *mainloop_api = NULL;
-static uint32_t module_idx = PA_INVALID_INDEX;
-static int sink_num = 0;
-static char sink_name[256] = { 0, };
-static const char *bname = NULL;
-
-/* This is called whenever the context status changes */
-static void context_state_callback(pa_context *c, void *userdata) {
- fail_unless(c != NULL);
-
- switch (pa_context_get_state(c)) {
- case PA_CONTEXT_CONNECTING:
- case PA_CONTEXT_AUTHORIZING:
- case PA_CONTEXT_SETTING_NAME:
- break;
-
- case PA_CONTEXT_READY:
- fprintf(stderr, "Connection established.\n");
- pa_threaded_mainloop_signal(mainloop, false);
- break;
-
- case PA_CONTEXT_TERMINATED:
- mainloop_api->quit(mainloop_api, 0);
- pa_threaded_mainloop_signal(mainloop, false);
- break;
-
- case PA_CONTEXT_FAILED:
- mainloop_api->quit(mainloop_api, 0);
- pa_threaded_mainloop_signal(mainloop, false);
- fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c)));
- fail();
- break;
-
- default:
- fail();
- }
-}
-
-static void module_index_cb(pa_context *c, uint32_t idx, void *userdata) {
- fail_unless(idx != PA_INVALID_INDEX);
-
- module_idx = idx;
-
- pa_threaded_mainloop_signal(mainloop, false);
-}
+static pa_test_context *ctx = NULL;
+static uint32_t sink_idx = PA_INVALID_INDEX;
+static const char *binary_name = NULL;
+/* We fill in fake AC3 data in terms of the corresponding PCM sample spec (S16LE, 2ch, at the given rate) */
+int16_t data[RATE * 2] = { 0, }; /* one second space */
static void success_cb(pa_context *c, int success, void *userdata) {
fail_unless(success != 0);
- pa_threaded_mainloop_signal(mainloop, false);
+ pa_threaded_mainloop_signal(ctx->mainloop, false);
}
static void passthrough_teardown() {
- pa_operation *o;
-
- pa_threaded_mainloop_lock(mainloop);
-
- if (module_idx != PA_INVALID_INDEX) {
- o = pa_context_unload_module(context, module_idx, success_cb, NULL);
- WAIT_FOR_OPERATION(o);
- }
-
- pa_context_disconnect(context);
- pa_context_unref(context);
-
- pa_threaded_mainloop_unlock(mainloop);
-
- pa_threaded_mainloop_stop(mainloop);
- pa_threaded_mainloop_free(mainloop);
+ pa_test_context_free(ctx);
}
static void passthrough_setup() {
- char modargs[128];
- pa_operation *o;
- int r;
+ ctx = pa_test_context_new(binary_name);
- /* Set up a new main loop */
- mainloop = pa_threaded_mainloop_new();
- fail_unless(mainloop != NULL);
-
- mainloop_api = pa_threaded_mainloop_get_api(mainloop);
-
- pa_threaded_mainloop_lock(mainloop);
-
- pa_threaded_mainloop_start(mainloop);
-
- context = pa_context_new(mainloop_api, bname);
- fail_unless(context != NULL);
-
- pa_context_set_state_callback(context, context_state_callback, NULL);
-
- /* Connect the context */
- r = pa_context_connect(context, NULL, 0, NULL);
- fail_unless(r == 0);
-
- pa_threaded_mainloop_wait(mainloop);
-
- fail_unless(pa_context_get_state(context) == PA_CONTEXT_READY);
-
- pa_snprintf(sink_name, sizeof(sink_name), "%s-%d", SINK_NAME, sink_num);
- pa_snprintf(modargs, sizeof(modargs), "sink_name='%s' formats='ac3-iec61937, format.rate=\"[32000, 44100, 48000]\" format.channels=\"6\"; pcm'", sink_name);
-
- o = pa_context_load_module(context, "module-null-sink", modargs, module_index_cb, NULL);
- WAIT_FOR_OPERATION(o);
-
- pa_threaded_mainloop_unlock(mainloop);
-
- return;
-}
-
-static void nop_free_cb(void *p) {}
-
-static void underflow_cb(struct pa_stream *s, void *userdata) {
- fprintf(stderr, "Stream finished\n");
- pa_threaded_mainloop_signal(mainloop, false);
-}
-
-/* This routine is called whenever the stream state changes */
-static void stream_state_callback(pa_stream *s, void *userdata) {
- /* We fill in fake AC3 data in terms of the corresponding PCM sample spec (S16LE, 2ch, at the given rate) */
- int16_t data[RATE * 2] = { 0, }; /* one second space */
-
- fail_unless(s != NULL);
-
- switch (pa_stream_get_state(s)) {
- case PA_STREAM_UNCONNECTED:
- case PA_STREAM_CREATING:
- break;
-
- case PA_STREAM_TERMINATED:
- pa_threaded_mainloop_signal(mainloop, false);
- break;
-
- case PA_STREAM_READY: {
- int r;
-
- r = pa_stream_write(s, data, sizeof(data), nop_free_cb, 0, PA_SEEK_ABSOLUTE);
- fail_unless(r == 0);
-
- /* Be notified when this stream is drained */
- pa_stream_set_underflow_callback(s, underflow_cb, userdata);
-
- pa_threaded_mainloop_signal(mainloop, false);
- break;
- }
-
- case PA_STREAM_FAILED:
- fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
- pa_threaded_mainloop_signal(mainloop, false);
- break;
-
- default:
- fail();
- }
+ sink_idx = pa_test_context_load_null_sink(ctx,
+ "formats='ac3-iec61937, format.rate=\"[32000, 44100, 48000]\" format.channels=\"6\"; pcm'");
}
static pa_stream* connect_stream() {
- int r;
pa_stream *s;
- pa_format_info *formats[1];
+ pa_format_info *format;
- pa_threaded_mainloop_lock(mainloop);
-
- formats[0] = pa_format_info_new();
- formats[0]->encoding = PA_ENCODING_AC3_IEC61937;
+ format = pa_format_info_new();
+ format->encoding = PA_ENCODING_AC3_IEC61937;
/* We set rate and channels to test that negotiation actually works. This
* must correspond to the rate and channels we configure module-null-sink
* for above. */
- pa_format_info_set_rate(formats[0], RATE);
- pa_format_info_set_channels(formats[0], CHANNELS);
+ pa_format_info_set_rate(format, RATE);
+ pa_format_info_set_channels(format, CHANNELS);
- s = pa_stream_new_extended(context, "passthrough test", formats, 1, NULL);
+ s = pa_test_context_create_stream(ctx, "passthrough test", sink_idx, format, PA_STREAM_NOFLAGS, data, sizeof(data));
fail_unless(s != NULL);
- pa_stream_set_state_callback(s, stream_state_callback, NULL);
- r = pa_stream_connect_playback(s, sink_name, NULL, PA_STREAM_NOFLAGS, NULL, NULL);
-
- fail_unless(r == 0);
-
- pa_threaded_mainloop_wait(mainloop);
-
- fail_unless(pa_stream_get_state(s) == PA_STREAM_READY);
-
- pa_threaded_mainloop_unlock(mainloop);
+ pa_format_info_free(format);
return s;
}
static void disconnect_stream(pa_stream *s) {
- int r;
-
- pa_threaded_mainloop_lock(mainloop);
-
- r = pa_stream_disconnect(s);
- fail_unless(r == 0);
-
- pa_threaded_mainloop_wait(mainloop);
- fail_unless(pa_stream_get_state(s) == PA_STREAM_TERMINATED);
-
- pa_stream_unref(s);
-
- pa_threaded_mainloop_unlock(mainloop);
+ pa_test_context_destroy_stream(ctx, s);
}
START_TEST (passthrough_playback_test) {
@@ -256,36 +84,22 @@ START_TEST (passthrough_playback_test) {
stream = connect_stream();
- /* Wait for underflow_cb() */
- pa_threaded_mainloop_lock(mainloop);
- pa_threaded_mainloop_wait(mainloop);
+ /* Wait for the stream to be drained */
+ pa_threaded_mainloop_lock(ctx->mainloop);
+ pa_threaded_mainloop_wait(ctx->mainloop);
fail_unless(pa_stream_get_state(stream) == PA_STREAM_READY);
- pa_threaded_mainloop_unlock(mainloop);
+ pa_threaded_mainloop_unlock(ctx->mainloop);
disconnect_stream(stream);
}
END_TEST
-static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
+static bool check_sink_volume(const pa_sink_info *sink_info, void *userdata) {
pa_cvolume *v = (pa_cvolume *) userdata;
- if (eol)
- return;
+ pa_assert(v);
- *v = i->volume;
-
- pa_threaded_mainloop_signal(mainloop, false);
-}
-
-static void get_sink_volume(pa_cvolume *v) {
- pa_operation *o;
-
- pa_threaded_mainloop_lock(mainloop);
-
- o = pa_context_get_sink_info_by_name(context, sink_name, sink_info_cb, v);
- WAIT_FOR_OPERATION(o);
-
- pa_threaded_mainloop_unlock(mainloop);
+ return pa_cvolume_equal(&sink_info->volume, v);
}
START_TEST (passthrough_volume_test) {
@@ -296,28 +110,28 @@ START_TEST (passthrough_volume_test) {
pa_operation *o;
pa_cvolume volume, tmp;
- pa_threaded_mainloop_lock(mainloop);
+ pa_threaded_mainloop_lock(ctx->mainloop);
pa_cvolume_set(&volume, 2, PA_VOLUME_NORM / 2);
- o = pa_context_set_sink_volume_by_name(context, sink_name, &volume, success_cb, NULL);
- WAIT_FOR_OPERATION(o);
+ o = pa_context_set_sink_volume_by_index(ctx->context, sink_idx, &volume, success_cb, NULL);
+ WAIT_FOR_OPERATION(ctx, o);
- pa_threaded_mainloop_unlock(mainloop);
+ pa_threaded_mainloop_unlock(ctx->mainloop);
stream = connect_stream();
- pa_threaded_mainloop_lock(mainloop);
- pa_threaded_mainloop_wait(mainloop);
+ /* Wait for the stream to be drained */
+ pa_threaded_mainloop_lock(ctx->mainloop);
+ pa_threaded_mainloop_wait(ctx->mainloop);
fail_unless(PA_STREAM_IS_GOOD(pa_stream_get_state(stream)));
- pa_threaded_mainloop_unlock(mainloop);
+ pa_threaded_mainloop_unlock(ctx->mainloop);
- get_sink_volume(&tmp);
- fail_unless(pa_cvolume_is_norm(&tmp));
+ pa_cvolume_set(&tmp, 2, PA_VOLUME_NORM);
+ fail_unless(pa_test_context_check_sink(ctx, sink_idx, check_sink_volume, &tmp));
disconnect_stream(stream);
- get_sink_volume(&tmp);
- fail_unless(pa_cvolume_equal(&volume, &tmp));
+ fail_unless(pa_test_context_check_sink(ctx, sink_idx, check_sink_volume, &volume));
}
END_TEST
@@ -327,13 +141,12 @@ int main(int argc, char *argv[]) {
TCase *tc;
SRunner *sr;
- bname = argv[0];
+ binary_name = argv[0];
s = suite_create("Passthrough");
tc = tcase_create("passthrough");
tcase_add_checked_fixture(tc, passthrough_setup, passthrough_teardown);
tcase_add_test(tc, passthrough_playback_test);
- sink_num++;
tcase_add_test(tc, passthrough_volume_test);
suite_add_tcase(s, tc);
diff --git a/src/tests/reconfigure-test.c b/src/tests/reconfigure-test.c
new file mode 100644
index 000000000..93c64125d
--- /dev/null
+++ b/src/tests/reconfigure-test.c
@@ -0,0 +1,107 @@
+/***
+ 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, see .
+***/
+
+#ifdef HAVE_CONFIG_H
+#include
+#endif
+
+#include "test-util.h"
+
+#include
+
+#include
+
+pa_test_context *ctx;
+uint32_t sink_idx = PA_INVALID_INDEX;
+static const char *binary_name = NULL;
+
+static void reconfigure_setup() {
+ ctx = pa_test_context_new(binary_name);
+
+ sink_idx = pa_test_context_load_null_sink(ctx, "avoid_processing=true");
+}
+
+static void reconfigure_teardown() {
+ pa_test_context_free(ctx);
+}
+
+#define SAMPLE_FORMAT PA_SAMPLE_S24_32LE
+#define RATE 384000
+#define CHANNELS 8
+
+static bool check_sink_format(const pa_sink_info *i, void *userdata) {
+ pa_channel_map *map = (pa_channel_map *) userdata;
+
+ pa_assert(map);
+
+ return (i->sample_spec.format == SAMPLE_FORMAT) &&
+ (i->sample_spec.rate == RATE) &&
+ (i->sample_spec.channels == CHANNELS) &&
+ pa_channel_map_equal(&i->channel_map, map);
+}
+
+START_TEST (reconfigure_test) {
+ pa_format_info *format;
+ pa_stream *s;
+ int rate = RATE;
+ int channels = CHANNELS;
+ pa_sample_format_t sample_format = SAMPLE_FORMAT;
+ pa_channel_map map;
+ /* Prepare 0.25s data, don't want more since RATE is quite high */
+ uint32_t data[RATE * CHANNELS / 4] = { 0, };
+
+ /* Pick a non-standard channel mapping */
+ pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_AUX);
+
+ format = pa_format_info_new();
+ format->encoding = PA_ENCODING_PCM;
+ pa_format_info_set_sample_format(format, sample_format);
+ pa_format_info_set_rate(format, rate);
+ pa_format_info_set_channels(format, channels);
+ pa_format_info_set_channel_map(format, &map);
+
+ s = pa_test_context_create_stream(ctx, "reconfigure test", sink_idx, format, PA_STREAM_PASSTHROUGH, data, sizeof(data));
+ fail_unless(s != NULL);
+
+ pa_test_context_check_sink(ctx, sink_idx, check_sink_format, &map);
+
+ pa_test_context_destroy_stream(ctx, s);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ binary_name = argv[0];
+
+ s = suite_create("Reconfigure");
+ tc = tcase_create("reconfigure");
+ tcase_add_checked_fixture(tc, reconfigure_setup, reconfigure_teardown);
+ tcase_add_test(tc, reconfigure_test);
+ tcase_set_timeout(tc, 2);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/sync-playback.c b/src/tests/sync-playback.c
index 3c356a750..522d06368 100644
--- a/src/tests/sync-playback.c
+++ b/src/tests/sync-playback.c
@@ -40,7 +40,7 @@
static pa_context *context = NULL;
static pa_stream *streams[NSTREAMS];
static pa_mainloop_api *mainloop_api = NULL;
-static const char *bname = NULL;
+static const char *binary_name = NULL;
static float data[SAMPLE_HZ]; /* one second space */
@@ -169,7 +169,7 @@ START_TEST (sync_playback_test) {
mainloop_api = pa_mainloop_get_api(m);
- context = pa_context_new(mainloop_api, bname);
+ context = pa_context_new(mainloop_api, binary_name);
fail_unless(context != NULL);
pa_context_set_state_callback(context, context_state_callback, NULL);
@@ -202,7 +202,7 @@ int main(int argc, char *argv[]) {
TCase *tc;
SRunner *sr;
- bname = argv[0];
+ binary_name = argv[0];
s = suite_create("Sync Playback");
tc = tcase_create("syncplayback");
diff --git a/src/tests/test-util.c b/src/tests/test-util.c
new file mode 100644
index 000000000..8bb4663e9
--- /dev/null
+++ b/src/tests/test-util.c
@@ -0,0 +1,325 @@
+/***
+ 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, see .
+***/
+
+#ifdef HAVE_CONFIG_H
+#include
+#endif
+
+#include "test-util.h"
+
+#include
+#include
+
+/* This is called whenever the context status changes */
+static void context_state_callback(pa_context *c, void *userdata) {
+ pa_test_context *ctx = (pa_test_context *) userdata;
+
+ pa_assert(c);
+ pa_assert(ctx);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY:
+ pa_log_info("Connection established.\n");
+ pa_threaded_mainloop_signal(ctx->mainloop, false);
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ ctx->mainloop_api->quit(ctx->mainloop_api, 0);
+ pa_threaded_mainloop_signal(ctx->mainloop, false);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ ctx->mainloop_api->quit(ctx->mainloop_api, 0);
+ pa_threaded_mainloop_signal(ctx->mainloop, false);
+ pa_log_error("Context error: %s\n", pa_strerror(pa_context_errno(c)));
+ pa_assert_not_reached();
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+}
+
+static void success_cb(pa_context *c, int success, void *userdata) {
+ pa_test_context *ctx = (pa_test_context *) userdata;
+
+ pa_assert(c);
+ pa_assert(ctx);
+ pa_assert(success != 0);
+
+ pa_threaded_mainloop_signal(ctx->mainloop, false);
+}
+
+pa_test_context* pa_test_context_new(const char *name) {
+ pa_test_context *ctx;
+ int r;
+
+ pa_assert(name);
+
+ ctx = pa_xnew0(pa_test_context, 1);
+
+ ctx->modules = pa_idxset_new(NULL, NULL);
+
+ /* Set up a new main loop */
+ ctx->mainloop = pa_threaded_mainloop_new();
+ pa_assert(ctx->mainloop);
+
+ ctx->mainloop_api = pa_threaded_mainloop_get_api(ctx->mainloop);
+
+ pa_threaded_mainloop_lock(ctx->mainloop);
+
+ pa_threaded_mainloop_start(ctx->mainloop);
+
+ ctx->context = pa_context_new(ctx->mainloop_api, name);
+ pa_assert(ctx->context);
+
+ pa_context_set_state_callback(ctx->context, context_state_callback, ctx);
+
+ /* Connect the context */
+ r = pa_context_connect(ctx->context, NULL, 0, NULL);
+ pa_assert(r == 0);
+
+ pa_threaded_mainloop_wait(ctx->mainloop);
+
+ pa_assert(pa_context_get_state(ctx->context) == PA_CONTEXT_READY);
+
+ pa_threaded_mainloop_unlock(ctx->mainloop);
+
+ return ctx;
+}
+
+void pa_test_context_free(pa_test_context *ctx) {
+ void *module;
+ uint32_t idx;
+
+ pa_threaded_mainloop_lock(ctx->mainloop);
+
+ PA_IDXSET_FOREACH(module, ctx->modules, idx) {
+ pa_operation *o;
+
+ o = pa_context_unload_module(ctx->context, PA_PTR_TO_UINT32(module), success_cb, ctx);
+
+ WAIT_FOR_OPERATION(ctx, o);
+ }
+
+ pa_idxset_free(ctx->modules, NULL);
+
+ pa_context_disconnect(ctx->context);
+ pa_context_unref(ctx->context);
+
+ pa_threaded_mainloop_unlock(ctx->mainloop);
+
+ pa_threaded_mainloop_stop(ctx->mainloop);
+ pa_threaded_mainloop_free(ctx->mainloop);
+
+ pa_xfree(ctx);
+}
+
+static void module_index_cb(pa_context *c, uint32_t idx, void *userdata) {
+ pa_test_context *ctx = (pa_test_context *) userdata;
+
+ pa_assert(c);
+ pa_assert(ctx);
+ pa_assert(idx != PA_INVALID_INDEX);
+
+ ctx->module_idx = idx;
+
+ pa_threaded_mainloop_signal(ctx->mainloop, false);
+}
+
+static void lookup_module_sink_idx(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
+ pa_test_context *ctx = (pa_test_context *) userdata;
+
+ pa_assert(ctx);
+
+ if (!i || eol) {
+ pa_threaded_mainloop_signal(ctx->mainloop, false);
+ return;
+ }
+
+ if (i->owner_module == ctx->module_idx)
+ ctx->sink_idx = i->index;
+}
+
+uint32_t pa_test_context_load_null_sink(pa_test_context *ctx, const char *modargs) {
+ pa_operation *o;
+
+ pa_assert(ctx);
+
+ pa_threaded_mainloop_lock(ctx->mainloop);
+
+ /* Load the module */
+ ctx->module_idx = PA_INVALID_INDEX;
+ o = pa_context_load_module(ctx->context, "module-null-sink", modargs, module_index_cb, ctx);
+ WAIT_FOR_OPERATION(ctx, o);
+
+ pa_assert(ctx->module_idx != PA_INVALID_INDEX);
+ pa_idxset_put(ctx->modules, PA_UINT32_TO_PTR(ctx->module_idx), NULL);
+
+ /* Look up the sink index corresponding to the module */
+ ctx->sink_idx = PA_INVALID_INDEX;
+ o = pa_context_get_sink_info_list(ctx->context, lookup_module_sink_idx, ctx);
+ WAIT_FOR_OPERATION(ctx, o);
+
+ pa_threaded_mainloop_unlock(ctx->mainloop);
+
+ pa_assert(ctx->sink_idx != PA_INVALID_INDEX);
+
+ return ctx->sink_idx;
+}
+
+static void nop_free_cb(void *p) {}
+
+static void underflow_cb(struct pa_stream *s, void *userdata) {
+ pa_test_context *ctx = (pa_test_context *) userdata;
+
+ pa_assert(ctx);
+
+ pa_log_info("Stream finished\n");
+
+ pa_threaded_mainloop_signal(ctx->mainloop, false);
+}
+
+/* This routine is called whenever the stream state changes */
+static void stream_state_callback(pa_stream *s, void *userdata) {
+ pa_test_context *ctx = (pa_test_context *) userdata;
+
+ pa_assert(s);
+ pa_assert(ctx);
+
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+
+ case PA_STREAM_TERMINATED:
+ pa_threaded_mainloop_signal(ctx->mainloop, false);
+ break;
+
+ case PA_STREAM_READY: {
+ int r;
+
+ r = pa_stream_write(s, ctx->data, ctx->length, nop_free_cb, 0, PA_SEEK_ABSOLUTE);
+ pa_assert(r == 0);
+
+ /* Be notified when this stream is drained */
+ pa_stream_set_underflow_callback(s, underflow_cb, userdata);
+
+ pa_threaded_mainloop_signal(ctx->mainloop, false);
+ break;
+ }
+
+ case PA_STREAM_FAILED:
+ pa_log_error("Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
+ pa_threaded_mainloop_signal(ctx->mainloop, false);
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+}
+
+pa_stream* pa_test_context_create_stream(pa_test_context *ctx, const char *name, uint32_t sink_idx, pa_format_info *format,
+ pa_stream_flags_t flags, void *data, size_t length) {
+ int r;
+ pa_stream *s;
+ pa_format_info *formats[1];
+ char sink_name[5];
+
+ pa_threaded_mainloop_lock(ctx->mainloop);
+
+ formats[0] = format;
+
+ ctx->data = data;
+ ctx->length = length;
+
+ s = pa_stream_new_extended(ctx->context, name, formats, 1, NULL);
+ pa_assert(s);
+
+ pa_snprintf(sink_name, sizeof(sink_name), "%u", sink_idx);
+
+ pa_stream_set_state_callback(s, stream_state_callback, ctx);
+ r = pa_stream_connect_playback(s, sink_name, NULL, flags, NULL, NULL);
+
+ pa_assert(r == 0);
+
+ pa_threaded_mainloop_wait(ctx->mainloop);
+
+ pa_assert(pa_stream_get_state(s) == PA_STREAM_READY);
+
+ pa_threaded_mainloop_unlock(ctx->mainloop);
+
+ return s;
+}
+
+void pa_test_context_destroy_stream(pa_test_context *ctx, pa_stream *s) {
+ int r;
+
+ pa_threaded_mainloop_lock(ctx->mainloop);
+
+ r = pa_stream_disconnect(s);
+ pa_assert(r == 0);
+
+ pa_threaded_mainloop_wait(ctx->mainloop);
+ pa_assert(pa_stream_get_state(s) == PA_STREAM_TERMINATED);
+
+ pa_stream_unref(s);
+
+ pa_threaded_mainloop_unlock(ctx->mainloop);
+}
+
+struct sink_info_pred {
+ pa_test_context *ctx;
+ pa_test_sink_info_pred_t func;
+ void *userdata;
+
+ bool ret;
+};
+
+static void check_sink_info(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
+ struct sink_info_pred *pred = (struct sink_info_pred *) userdata;
+
+ pa_assert(c);
+ pa_assert(pred);
+
+ if (i)
+ pred->ret = pred->func(i, pred->userdata);
+
+ pa_threaded_mainloop_signal(pred->ctx->mainloop, false);
+}
+
+bool pa_test_context_check_sink(pa_test_context *ctx, uint32_t idx, pa_test_sink_info_pred_t predicate, void *userdata) {
+ pa_operation *o;
+ struct sink_info_pred pred = { ctx, predicate, userdata, false };
+
+ pa_assert(ctx);
+ pa_assert(predicate);
+
+ pa_threaded_mainloop_lock(ctx->mainloop);
+
+ o = pa_context_get_sink_info_by_index(ctx->context, idx, check_sink_info, &pred);
+ WAIT_FOR_OPERATION(ctx, o);
+
+ pa_threaded_mainloop_unlock(ctx->mainloop);
+
+ return pred.ret;
+}
diff --git a/src/tests/test-util.h b/src/tests/test-util.h
new file mode 100644
index 000000000..3a20b962e
--- /dev/null
+++ b/src/tests/test-util.h
@@ -0,0 +1,74 @@
+#ifndef footestutilhfoo
+#define footestutilhfoo
+
+/***
+ 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, see .
+***/
+
+#include
+
+#include
+
+#include
+#include
+
+#define WAIT_FOR_OPERATION(ctx, o) \
+ do { \
+ while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { \
+ pa_threaded_mainloop_wait(ctx->mainloop); \
+ } \
+ \
+ pa_assert(pa_operation_get_state(o) == PA_OPERATION_DONE); \
+ pa_operation_unref(o); \
+ } while (false)
+
+typedef struct pa_test_context {
+ /* "Public" members */
+ pa_threaded_mainloop *mainloop;
+ pa_mainloop_api *mainloop_api;
+ pa_context *context;
+
+ /* "Private" bookkeeping */
+ pa_idxset *modules;
+ uint32_t module_idx, sink_idx; /* only used for module -> sink index lookup */
+ void *data;
+ size_t length;
+} pa_test_context;
+
+pa_test_context* pa_test_context_new(const char *name);
+void pa_test_context_free(pa_test_context *ctx);
+
+/* Loads a null sink with provided params to test with */
+uint32_t pa_test_context_load_null_sink(pa_test_context *ctx, const char *modargs);
+
+/* A stream is created and started. The function doesn't wait for the data to
+ * be played back, playback will continue in the background. The data buffer
+ * will be played only once, after which an underflow callback will call
+ * pa_threaded_mainloop_signal() so pa_threaded_mainloop_wait() can be used to
+ * wait for the stream to finish playing.
+ */
+pa_stream* pa_test_context_create_stream(pa_test_context *ctx, const char *name, uint32_t sink_idx, pa_format_info *format,
+ pa_stream_flags_t flags, void *data, size_t length);
+/* Clean up the stream */
+void pa_test_context_destroy_stream(pa_test_context *ctx, pa_stream *s);
+
+typedef bool (*pa_test_sink_info_pred_t)(const pa_sink_info *sink_info, void *userdata);
+
+/* Test the current state of the sink by providing a predicate function which
+ * can examine the sink's pa_sink_info for whatever condition is expected. */
+bool pa_test_context_check_sink(pa_test_context *ctx, uint32_t idx, pa_test_sink_info_pred_t predicate, void *userdata);
+
+#endif /* footestutilhfoo */
diff --git a/src/utils/pacat.c b/src/utils/pacat.c
index e656dee1b..5bbf135a6 100644
--- a/src/utils/pacat.c
+++ b/src/utils/pacat.c
@@ -100,6 +100,13 @@ static bool sample_spec_set = false;
static pa_channel_map channel_map;
static bool channel_map_set = false;
+/* If the encoding is set, we assume the pa_sample_spec will not be used, and
+ * that the pa_format_info will be. */
+static pa_encoding_t encoding;
+static bool encoding_set = false;
+
+pa_format_info *formats[1] = { NULL, };
+
static sf_count_t (*readf_function)(SNDFILE *_sndfile, void *ptr, sf_count_t frames) = NULL;
static sf_count_t (*writef_function)(SNDFILE *_sndfile, const void *ptr, sf_count_t frames) = NULL;
@@ -319,7 +326,7 @@ static void stream_state_callback(pa_stream *s, void *userdata) {
if (verbose) {
const pa_buffer_attr *a;
- char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ char map_str[PA_CHANNEL_MAP_SNPRINT_MAX], spec_str[PA_SAMPLE_SPEC_SNPRINT_MAX], format_str[PA_FORMAT_INFO_SNPRINT_MAX];
pa_log(_("Stream successfully created."));
@@ -335,9 +342,14 @@ static void stream_state_callback(pa_stream *s, void *userdata) {
}
}
- pa_log(_("Using sample spec '%s', channel map '%s'."),
- pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(s)),
- pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(s)));
+ if (!encoding_set) {
+ pa_log(_("Using sample spec '%s', channel map '%s'."),
+ pa_sample_spec_snprint(spec_str, sizeof(spec_str), pa_stream_get_sample_spec(s)),
+ pa_channel_map_snprint(map_str, sizeof(map_str), pa_stream_get_channel_map(s)));
+ } else {
+ pa_log(_("Using format '%s'."),
+ pa_format_info_snprint(format_str, sizeof(format_str), pa_stream_get_format_info(s)));
+ }
pa_log(_("Connected to device %s (index: %u, suspended: %s)."),
pa_stream_get_device_name(s),
@@ -449,7 +461,12 @@ static void context_state_callback(pa_context *c, void *userdata) {
if (verbose)
pa_log(_("Connection established.%s"), CLEAR_LINE);
- if (!(stream = pa_stream_new_with_proplist(c, NULL, &sample_spec, &channel_map, proplist))) {
+ if (!encoding_set)
+ stream = pa_stream_new_with_proplist(c, NULL, &sample_spec, &channel_map, proplist);
+ else
+ stream = pa_stream_new_extended(c, NULL, formats, 1, proplist);
+
+ if (!stream) {
pa_log(_("pa_stream_new() failed: %s"), pa_strerror(pa_context_errno(c)));
goto fail;
}
@@ -692,6 +709,7 @@ static void help(const char *argv0) {
" --channels=CHANNELS The number of channels, 1 for mono, 2 for stereo\n"
" (defaults to 2)\n"
" --channel-map=CHANNELMAP Channel map to use instead of the default\n"
+ " --encoding=ENCODING Encoding to use for non-PCM audio\n"
" --fix-format Take the sample format from the sink/source the stream is\n"
" being connected to.\n"
" --fix-rate Take the sampling rate from the sink/source the stream is\n"
@@ -721,6 +739,7 @@ enum {
ARG_SAMPLEFORMAT,
ARG_CHANNELS,
ARG_CHANNELMAP,
+ ARG_ENCODING,
ARG_FIX_FORMAT,
ARG_FIX_RATE,
ARG_FIX_CHANNELS,
@@ -762,6 +781,7 @@ int main(int argc, char *argv[]) {
{"format", 1, NULL, ARG_SAMPLEFORMAT},
{"channels", 1, NULL, ARG_CHANNELS},
{"channel-map", 1, NULL, ARG_CHANNELMAP},
+ {"encoding", 1, NULL, ARG_ENCODING},
{"fix-format", 0, NULL, ARG_FIX_FORMAT},
{"fix-rate", 0, NULL, ARG_FIX_RATE},
{"fix-channels", 0, NULL, ARG_FIX_CHANNELS},
@@ -908,6 +928,20 @@ int main(int argc, char *argv[]) {
channel_map_set = true;
break;
+ case ARG_ENCODING:
+ if ((encoding = pa_encoding_from_string(optarg)) == PA_ENCODING_INVALID) {
+ pa_log(_("Invalid encoding '%s'"), optarg);
+ goto quit;
+ }
+
+ if (encoding == PA_ENCODING_PCM) {
+ pa_log(_("The encoding parameter is only supported with non-PCM formats."));
+ goto quit;
+ }
+
+ encoding_set = true;
+ break;
+
case ARG_FIX_CHANNELS:
flags |= PA_STREAM_FIX_CHANNELS;
break;
@@ -1007,7 +1041,33 @@ int main(int argc, char *argv[]) {
}
}
- if (!pa_sample_spec_valid(&sample_spec)) {
+ if (encoding_set && !raw) {
+ pa_log(_("Cannot set encoding for non-raw mode"));
+ goto quit;
+ }
+
+ /* The capture path uses the sample spec to know how much to read, so let's
+ * not support that for now. */
+ if (encoding_set && mode != PLAYBACK) {
+ pa_log(_("Cannot set encoding for capture"));
+ goto quit;
+ }
+
+ if (encoding_set) {
+ formats[0] = pa_format_info_new();
+
+ formats[0]->encoding = encoding;
+ pa_format_info_set_rate(formats[0], sample_spec.rate);
+ pa_format_info_set_channels(formats[0], sample_spec.channels);
+
+ /* Fix up the sample spec based on the format we have */
+ pa_format_info_to_sample_spec(formats[0], &sample_spec, NULL);
+
+ if (!pa_format_info_valid(formats[0])) {
+ pa_log(_("Invalid format specification."));
+ goto quit;
+ }
+ } else if (!pa_sample_spec_valid(&sample_spec)) {
pa_log(_("Invalid sample specification"));
goto quit;
}
@@ -1132,12 +1192,20 @@ int main(int argc, char *argv[]) {
}
if (verbose) {
- char tss[PA_SAMPLE_SPEC_SNPRINT_MAX], tcm[PA_CHANNEL_MAP_SNPRINT_MAX];
+ if (!encoding_set) {
+ char spec_str[PA_SAMPLE_SPEC_SNPRINT_MAX], map_str[PA_CHANNEL_MAP_SNPRINT_MAX];
- pa_log(_("Opening a %s stream with sample specification '%s' and channel map '%s'."),
- mode == RECORD ? _("recording") : _("playback"),
- pa_sample_spec_snprint(tss, sizeof(tss), &sample_spec),
- pa_channel_map_snprint(tcm, sizeof(tcm), &channel_map));
+ pa_log(_("Opening a %s stream with sample specification '%s' and channel map '%s'."),
+ mode == RECORD ? _("recording") : _("playback"),
+ pa_sample_spec_snprint(spec_str, sizeof(spec_str), &sample_spec),
+ pa_channel_map_snprint(map_str, sizeof(map_str), &channel_map));
+ } else {
+ char format_str[PA_FORMAT_INFO_SNPRINT_MAX];
+
+ pa_log(_("Opening a %s stream with format specification '%s'."),
+ mode == RECORD ? _("recording") : _("playback"),
+ pa_format_info_snprint(format_str, sizeof(format_str), formats[0]));
+ }
}
/* Fill in client name if none was set */