Merge branch 'reconfigure' into 'master'

Improvements to reconfiguration

See merge request pulseaudio/pulseaudio!24
This commit is contained in:
Arun Raghavan 2025-10-02 18:50:29 +00:00
commit d619b3d189
30 changed files with 1325 additions and 528 deletions

View file

@ -148,6 +148,14 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
<opt>aux31</opt>.</p></optdesc>
</option>
<option>
<p><opt>--encoding</opt></p>
<optdesc><p>The encoding to use while playing non-PCM audio (with
the <opt>--raw</opt> flag). Valid options can be found at
https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/SupportedAudioFormats/
</p></optdesc>
</option>
<option>
<p><opt>--fix-format</opt></p>
<optdesc><p>If passed, the sample format of the stream is changed to the native format of the sink the stream is connected to.</p></optdesc>

View file

@ -132,6 +132,16 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
rates.</p>
</option>
<option>
<p><opt>avoid-processing=</opt> If set, try to configure the
device to avoid resampling, sample format conversion and channel mixing. This only works on devices which
support such reconfiguration, and when no other streams are
already playing or capturing audio. The device will also not be
configured to a rate less than the default and alternate sample
rates, a channel count less than the default channels, or a sample
format that is smaller than the default sample format.</p>
</option>
<option>
<p><opt>enable-remixing=</opt> If disabled never upmix or
downmix channels to different channel maps. Instead, do a simple

View file

@ -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='

View file

@ -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]' \

View file

@ -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));

View file

@ -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,

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -69,6 +69,7 @@ PA_MODULE_USAGE(
"paths_dir=<directory containing the path configuration files> "
"use_ucm=<load use case manager> "
"avoid_resampling=<use stream original sample rate if possible?> "
"avoid_processing=<use stream original sample spec if possible?> "
"control=<name of mixer control> "
);
@ -98,6 +99,7 @@ static const char* const valid_modargs[] = {
"paths_dir",
"use_ucm",
"avoid_resampling",
"avoid_processing",
"control",
NULL
};

View file

@ -21,9 +21,10 @@
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pulse/rtclock.h>
@ -35,6 +36,7 @@
#include <pulsecore/macro.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h>
#include <pulsecore/core-error.h>
#include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h>
@ -52,9 +54,12 @@ PA_MODULE_USAGE(
"format=<sample format> "
"rate=<sample rate> "
"channels=<number of channels> "
"channel_map=<channel map>"
"formats=<semi-colon separated sink formats>"
"norewinds=<disable rewinds>");
"channel_map=<channel map> "
"formats=<semi-colon separated sink formats> "
"norewinds=<disable rewinds> "
"dump=<path to file to dump to, automatically prefixed with /tmp/pulse-> "
"avoid_processing=<use stream original sample spec if possible?> "
);
#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);
}

View file

@ -47,7 +47,8 @@ PA_MODULE_USAGE(
"ignore_dB=<ignore dB information from the device?> "
"deferred_volume=<syncronize sw and hw volume changes in IO-thread?> "
"use_ucm=<use ALSA UCM for card configuration?> "
"avoid_resampling=<use stream original sample rate if possible?>");
"avoid_resampling=<use stream original sample spec if possible?> "
"avoid_processing=<use stream original sample spec if possible?>");
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;

View file

@ -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;

View file

@ -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 */

View file

@ -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));

View file

@ -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 */

View file

@ -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);

View file

@ -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));

View file

@ -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 */

View file

@ -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");

View file

@ -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 ] ],

View file

@ -19,234 +19,62 @@
#include <config.h>
#endif
#include <stdio.h>
#include <stdbool.h>
#include <check.h>
#include <pulse/pulseaudio.h>
#include <pulsecore/core-util.h>
#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);

View file

@ -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 <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "test-util.h"
#include <check.h>
#include <pulse/pulseaudio.h>
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;
}

View file

@ -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");

325
src/tests/test-util.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "test-util.h"
#include <pulsecore/core-util.h>
#include <pulsecore/macro.h>
/* 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;
}

74
src/tests/test-util.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
***/
#include <stdbool.h>
#include <pulse/pulseaudio.h>
#include <pulsecore/idxset.h>
#include <pulsecore/macro.h>
#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 */

View file

@ -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 */