mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-10-29 05:40:23 -04:00
Merge branch 'reconfigure' into 'master'
Improvements to reconfiguration See merge request pulseaudio/pulseaudio!24
This commit is contained in:
commit
d619b3d189
30 changed files with 1325 additions and 528 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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='
|
||||
|
|
|
|||
|
|
@ -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]' \
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 ] ],
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
107
src/tests/reconfigure-test.c
Normal file
107
src/tests/reconfigure-test.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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
325
src/tests/test-util.c
Normal 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
74
src/tests/test-util.h
Normal 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 */
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue