diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index 17360a461..93c52c939 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -64,6 +64,26 @@ PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.pcm"); #define MIN_PERIOD 64 +#define MIN_PERIOD_BYTES (128) +#define MAX_PERIOD_BYTES (2*1024*1024) + +#define MIN_BUFFER_BYTES (2*MIN_PERIOD_BYTES) +#define MAX_BUFFER_BYTES (2*MAX_PERIOD_BYTES) + +struct params { + const char *node_name; + const char *server_name; + const char *playback_node; + const char *capture_node; + const char *role; + snd_pcm_format_t format; + int rate; + int channels; + int period_bytes; + int buffer_bytes; + uint32_t flags; +}; + typedef struct { snd_pcm_ioplug_t io; @@ -178,6 +198,7 @@ static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) pw_thread_loop_destroy(pw->main_loop); free(pw->node_name); free(pw->target); + free(pw->role); snd_output_close(pw->output); fclose(pw->log_file); free(pw); @@ -942,8 +963,7 @@ static snd_pcm_ioplug_callback_t pipewire_pcm_callback = { .query_chmaps = snd_pcm_pipewire_query_chmaps, }; -static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, int rate, - snd_pcm_format_t format, int channels, int period_bytes) +static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, struct params *p) { unsigned int access_list[] = { SND_PCM_ACCESS_MMAP_INTERLEAVED, @@ -975,26 +995,38 @@ static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, int rate, int max_channels; int min_period_bytes; int max_period_bytes; + int min_buffer_bytes; + int max_buffer_bytes; int err; - if (rate > 0) { - min_rate = max_rate = rate; + if (p->rate > 0) { + min_rate = max_rate = SPA_CLAMP(p->rate, 1, MAX_RATE); } else { min_rate = 1; max_rate = MAX_RATE; } - if (channels > 0) { - min_channels = max_channels = channels; + if (p->channels > 0) { + min_channels = max_channels = SPA_CLAMP(p->channels, 1, MAX_CHANNELS); } else { min_channels = 1; max_channels = MAX_CHANNELS; } - if (period_bytes > 0) { - min_period_bytes = max_period_bytes = period_bytes; + if (p->period_bytes > 0) { + min_period_bytes = max_period_bytes = SPA_CLAMP(p->period_bytes, + MIN_PERIOD_BYTES, MAX_PERIOD_BYTES); } else { - min_period_bytes = 128; - max_period_bytes = 2*1024*1024; + min_period_bytes = MIN_PERIOD_BYTES; + max_period_bytes = MAX_PERIOD_BYTES; } + if (p->buffer_bytes > 0) { + min_buffer_bytes = max_buffer_bytes = SPA_CLAMP(p->buffer_bytes, + MIN_BUFFER_BYTES, MAX_BUFFER_BYTES); + } else { + min_buffer_bytes = MIN_BUFFER_BYTES; + max_buffer_bytes = MAX_BUFFER_BYTES; + } + if (min_period_bytes * 2 > max_buffer_bytes) + min_period_bytes = max_period_bytes = max_buffer_bytes / 2; if ((err = snd_pcm_ioplug_set_param_list(&pw->io, SND_PCM_IOPLUG_HW_ACCESS, SPA_N_ELEMENTS(access_list), access_list)) < 0 || @@ -1003,8 +1035,8 @@ static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, int rate, (err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_RATE, min_rate, max_rate)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_BUFFER_BYTES, - MIN_BUFFERS*min_period_bytes, - MIN_BUFFERS*max_period_bytes)) < 0 || + min_buffer_bytes, + max_buffer_bytes)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, min_period_bytes, @@ -1015,10 +1047,10 @@ static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, int rate, return err; } - if (format != SND_PCM_FORMAT_UNKNOWN) { + if (p->format != SND_PCM_FORMAT_UNKNOWN) { err = snd_pcm_ioplug_set_param_list(&pw->io, SND_PCM_IOPLUG_HW_FORMAT, - 1, (unsigned int *)&format); + 1, (unsigned int *)&p->format); if (err < 0) { pw_log_warn("Can't set param list: %s", snd_strerror(err)); return err; @@ -1075,60 +1107,77 @@ static cookie_io_functions_t io_funcs = { .write = log_write, }; -static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, - const char *node_name, - const char *server_name, - const char *playback_node, - const char *capture_node, - const char *role, - snd_pcm_stream_t stream, - int mode, - uint32_t flags, - int rate, - snd_pcm_format_t format, - int channels, - int period_bytes) +static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, + struct params *p, snd_pcm_stream_t stream, int mode) { snd_pcm_pipewire_t *pw; int err; const char *str; struct pw_properties *props = NULL; struct pw_loop *loop; + uint32_t val; assert(pcmp); pw = calloc(1, sizeof(*pw)); if (!pw) return -ENOMEM; + props = pw_properties_new(NULL, NULL); + if (props == NULL) { + err = -errno; + goto error; + } + + str = getenv("PIPEWIRE_ALSA"); + if (str != NULL) { + pw_properties_update_string(props, str, strlen(str)); + if ((str = pw_properties_get(props, "alsa.format"))) + p->format = snd_pcm_format_value(str); + if ((str = pw_properties_get(props, "alsa.rate")) && + spa_atou32(str, &val, 0)) + p->rate = val; + if ((str = pw_properties_get(props, "alsa.channels")) && + spa_atou32(str, &val, 0)) + p->channels = val; + if ((str = pw_properties_get(props, "alsa.period-bytes")) && + spa_atou32(str, &val, 0)) + p->period_bytes = val; + if ((str = pw_properties_get(props, "alsa.buffer-bytes")) && + spa_atou32(str, &val, 0)) + p->buffer_bytes = val; + } + str = getenv("PIPEWIRE_REMOTE"); if (str != NULL && str[0] != '\0') - server_name = str; + p->server_name = str; str = getenv("PIPEWIRE_NODE"); pw_log_debug("%p: open name:%s stream:%s mode:%d flags:%08x rate:%d format:%s " - "channels:%d period-bytes:%d target:'%s'", pw, name, - snd_pcm_stream_name(stream), mode, flags, rate, - snd_pcm_format_name(format), channels, period_bytes, str); + "channels:%d period-bytes:%d buffer-bytes:%d target:'%s'", pw, p->node_name, + snd_pcm_stream_name(stream), mode, p->flags, p->rate, + snd_pcm_format_name(p->format), p->channels, p->period_bytes, + p->buffer_bytes, str); pw->fd = -1; pw->io.poll_fd = -1; - pw->flags = flags; + pw->flags = p->flags; pw->log_file = fopencookie(pw, "w", io_funcs); if (pw->log_file == NULL) { pw_log_error("can't create log file: %m"); - return -errno; + err = -errno; + goto error; } if ((err = snd_output_stdio_attach(&pw->output, pw->log_file, 0)) < 0) { pw_log_error("can't attach log file: %s", snd_strerror(err)); - return err; + goto error; } - if (node_name == NULL) + if (p->node_name == NULL) pw->node_name = spa_aprintf("ALSA %s", stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); else - pw->node_name = strdup(node_name); + pw->node_name = strdup(p->node_name); if (pw->node_name == NULL) { err = -errno; @@ -1140,12 +1189,12 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, pw->target = strdup(str); else { if (stream == SND_PCM_STREAM_PLAYBACK) - pw->target = playback_node ? strdup(playback_node) : NULL; + pw->target = p->playback_node ? strdup(p->playback_node) : NULL; else - pw->target = capture_node ? strdup(capture_node) : NULL; + pw->target = p->capture_node ? strdup(p->capture_node) : NULL; } - pw->role = (role && *role) ? strdup(role) : NULL; + pw->role = (p->role && *p->role) ? strdup(p->role) : NULL; pw->main_loop = pw_thread_loop_new("alsa-pipewire", NULL); if (pw->main_loop == NULL) { @@ -1163,13 +1212,11 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, goto error; } - props = pw_properties_new(NULL, NULL); - pw_properties_setf(props, PW_KEY_APP_NAME, "PipeWire ALSA [%s]", pw_get_prgname()); - if (server_name) - pw_properties_set(props, PW_KEY_REMOTE_NAME, server_name); + if (p->server_name) + pw_properties_set(props, PW_KEY_REMOTE_NAME, p->server_name); if ((err = pw_thread_loop_start(pw->main_loop)) < 0) goto error; @@ -1201,15 +1248,13 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, #endif pw->io.flags |= SND_PCM_IOPLUG_FLAG_MONOTONIC; - if ((err = snd_pcm_ioplug_create(&pw->io, name, stream, mode)) < 0) + if ((err = snd_pcm_ioplug_create(&pw->io, p->node_name, stream, mode)) < 0) goto error; - - if ((err = pipewire_set_hw_constraint(pw, rate, format, channels, - period_bytes)) < 0) + if ((err = pipewire_set_hw_constraint(pw, p)) < 0) goto error; - pw_log_debug("%p: opened name:%s stream:%s mode:%d", pw, name, + pw_log_debug("%p: opened name:%s stream:%s mode:%d", pw, p->node_name, snd_pcm_stream_name(pw->io.stream), mode); *pcmp = pw->io.pcm; @@ -1217,7 +1262,7 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, return 0; error: - pw_log_debug("%p: failed to open %s :%s", pw, name, spa_strerror(err)); + pw_log_debug("%p: failed to open %s :%s", pw, p->node_name, spa_strerror(err)); pw_properties_free(props); snd_pcm_pipewire_free(pw); return err; @@ -1228,16 +1273,7 @@ SPA_EXPORT SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) { snd_config_iterator_t i, next; - const char *node_name = NULL; - const char *server_name = NULL; - const char *playback_node = NULL; - const char *capture_node = NULL; - const char *role = NULL; - snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN; - int rate = 0; - int channels = 0; - int period_bytes = 0; - uint32_t flags = 0; + struct params params; int err; pw_init(NULL, NULL); @@ -1245,6 +1281,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) return -ENOTSUP; PW_LOG_TOPIC_INIT(alsa_log_topic); + spa_zero(params); snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); @@ -1254,35 +1291,35 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) if (spa_streq(id, "comment") || spa_streq(id, "type") || spa_streq(id, "hint")) continue; if (spa_streq(id, "name")) { - snd_config_get_string(n, &node_name); + snd_config_get_string(n, ¶ms.node_name); continue; } if (spa_streq(id, "server")) { - snd_config_get_string(n, &server_name); + snd_config_get_string(n, ¶ms.server_name); continue; } if (spa_streq(id, "playback_node")) { - snd_config_get_string(n, &playback_node); + snd_config_get_string(n, ¶ms.playback_node); continue; } if (spa_streq(id, "capture_node")) { - snd_config_get_string(n, &capture_node); + snd_config_get_string(n, ¶ms.capture_node); continue; } if (spa_streq(id, "role")) { - snd_config_get_string(n, &role); + snd_config_get_string(n, ¶ms.role); continue; } if (spa_streq(id, "exclusive")) { if (snd_config_get_bool(n)) - flags |= PW_STREAM_FLAG_EXCLUSIVE; + params.flags |= PW_STREAM_FLAG_EXCLUSIVE; continue; } if (spa_streq(id, "rate")) { long val; if (snd_config_get_integer(n, &val) == 0) - rate = val; + params.rate = val; else SNDERR("%s: invalid type", id); continue; @@ -1291,8 +1328,8 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) const char *str; if (snd_config_get_string(n, &str) == 0) { - format = snd_pcm_format_value(str); - if (format == SND_PCM_FORMAT_UNKNOWN) + params.format = snd_pcm_format_value(str); + if (*str && params.format == SND_PCM_FORMAT_UNKNOWN) SNDERR("%s: invalid value %s", id, str); } else { SNDERR("%s: invalid type", id); @@ -1303,7 +1340,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) long val; if (snd_config_get_integer(n, &val) == 0) - channels = val; + params.channels = val; else SNDERR("%s: invalid type", id); continue; @@ -1312,7 +1349,16 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) long val; if (snd_config_get_integer(n, &val) == 0) - period_bytes = val; + params.period_bytes = val; + else + SNDERR("%s: invalid type", id); + continue; + } + if (spa_streq(id, "buffer_bytes")) { + long val; + + if (snd_config_get_integer(n, &val) == 0) + params.buffer_bytes = val; else SNDERR("%s: invalid type", id); continue; @@ -1321,9 +1367,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) return -EINVAL; } - err = snd_pcm_pipewire_open(pcmp, name, node_name, server_name, playback_node, - capture_node, role, stream, mode, flags, rate, format, - channels, period_bytes); + err = snd_pcm_pipewire_open(pcmp, ¶ms, stream, mode); return err; } diff --git a/pipewire-alsa/conf/50-pipewire.conf b/pipewire-alsa/conf/50-pipewire.conf index f7e58472b..a3a08a61c 100644 --- a/pipewire-alsa/conf/50-pipewire.conf +++ b/pipewire-alsa/conf/50-pipewire.conf @@ -4,9 +4,14 @@ defaults.pipewire.server "pipewire-0" defaults.pipewire.node "-1" defaults.pipewire.exclusive false defaults.pipewire.role "" +defaults.pipewire.rate 0 +defaults.pipewire.format "" +defaults.pipewire.channels 0 +defaults.pipewire.period_bytes 0 +defaults.pipewire.buffer_bytes 0 pcm.pipewire { - @args [ SERVER NODE EXCLUSIVE ROLE ] + @args [ SERVER NODE EXCLUSIVE ROLE RATE FORMAT CHANNELS PERIOD_BYTES BUFFER_BYTES ] @args.SERVER { type string default { @@ -35,7 +40,41 @@ pcm.pipewire { name defaults.pipewire.role } } - + @args.RATE { + type integer + default { + @func refer + name defaults.pipewire.rate + } + } + @args.FORMAT { + type string + default { + @func refer + name defaults.pipewire.format + } + } + @args.CHANNELS { + type integer + default { + @func refer + name defaults.pipewire.channels + } + } + @args.PERIOD_BYTES { + type integer + default { + @func refer + name defaults.pipewire.period_bytes + } + } + @args.BUFFER_BYTES { + type integer + default { + @func refer + name defaults.pipewire.buffer_bytes + } + } type pipewire server $SERVER @@ -43,6 +82,11 @@ pcm.pipewire { capture_node $NODE exclusive $EXCLUSIVE role $ROLE + rate $RATE + format $FORMAT + channels $CHANNELS + period_bytes $PERIOD_BYTES + buffer_bytes $BUFFER_BYTES hint { show on description "PipeWire Sound Server"