From e48a1595a67e41de41e87b8fc8022397cba6a382 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 10 Sep 2024 14:49:34 +0200 Subject: [PATCH 01/31] spa: v4l2: Add missing v4l2_log_topic_init() call to device/udev impl_init() Wireplumber creates SPA objects of the "api.v4l2.enum.udev" type without ever creating any "api.v4l2.source" objects. This was causing the v4l2_log_topic variable to never get initialized, causing spa_log_xxx() calls in v4l2-udev.c to not work. Call v4l2_log_topic_init() from v4l2-udev's impl_init() to fix this. v4l2-device's impl_init() is also missing a call to v4l2_log_topic_init(), add this as well. Signed-off-by: Hans de Goede --- spa/plugins/v4l2/v4l2-device.c | 1 + spa/plugins/v4l2/v4l2-udev.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/spa/plugins/v4l2/v4l2-device.c b/spa/plugins/v4l2/v4l2-device.c index 2379d2105..4257a01c1 100644 --- a/spa/plugins/v4l2/v4l2-device.c +++ b/spa/plugins/v4l2/v4l2-device.c @@ -231,6 +231,7 @@ impl_init(const struct spa_handle_factory *factory, handle->clear = impl_clear, this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + v4l2_log_topic_init(this->log); spa_hook_list_init(&this->hooks); diff --git a/spa/plugins/v4l2/v4l2-udev.c b/spa/plugins/v4l2/v4l2-udev.c index 7118b6239..2ff8438b2 100644 --- a/spa/plugins/v4l2/v4l2-udev.c +++ b/spa/plugins/v4l2/v4l2-udev.c @@ -695,6 +695,8 @@ impl_init(const struct spa_handle_factory *factory, this->notify.fd = -1; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + v4l2_log_topic_init(this->log); + this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); if (this->main_loop == NULL) { From ae50bb5dc02f04d6adae37e2d06cc5e8293e58af Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 22 Oct 2025 09:39:15 +0200 Subject: [PATCH 02/31] audio: don't limit channels to max positions We can have more channels than we have positions. --- spa/include/spa/param/audio/raw-json.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index edbb715d1..282e76ed5 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -64,7 +64,7 @@ spa_audio_info_raw_update(struct spa_audio_info_raw *info, const char *key, cons info->rate = v; } else if (spa_streq(key, SPA_KEY_AUDIO_CHANNELS)) { if (spa_atou32(val, &v, 0) && (force || info->channels == 0)) - info->channels = SPA_MIN(v, SPA_N_ELEMENTS(info->position)); + info->channels = v; } else if (spa_streq(key, SPA_KEY_AUDIO_POSITION)) { if (force || info->channels == 0) { if (spa_audio_parse_position_n(val, strlen(val), info->position, From 6465a63bf648caf827b9ce4e833d10beee9aafcf Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 22 Oct 2025 12:47:00 +0200 Subject: [PATCH 03/31] spa: parse the audio.position completetly Parse the audio.position spec completely so that we have the right number of channels but only store the first max_position channels. Also rename some field to make it clear that this is about the max number of channel positions. --- spa/include/spa/param/audio/raw-json.h | 9 +++++---- spa/plugins/alsa/alsa-pcm.c | 6 +++--- spa/plugins/alsa/alsa-pcm.h | 4 ++-- spa/plugins/filter-graph/filter-graph.c | 5 +++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index 282e76ed5..7ef1b27f3 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -29,7 +29,7 @@ extern "C" { SPA_API_AUDIO_RAW_JSON int spa_audio_parse_position_n(const char *str, size_t len, - uint32_t *position, uint32_t max_channels, uint32_t *n_channels) + uint32_t *position, uint32_t max_position, uint32_t *n_channels) { struct spa_json iter; char v[256]; @@ -38,9 +38,10 @@ spa_audio_parse_position_n(const char *str, size_t len, if (spa_json_begin_array_relax(&iter, str, len) <= 0) return 0; - while (spa_json_get_string(&iter, v, sizeof(v)) > 0 && - channels < max_channels) { - position[channels++] = spa_type_audio_channel_from_short_name(v); + while (spa_json_get_string(&iter, v, sizeof(v)) > 0) { + if (channels < max_position) + position[channels] = spa_type_audio_channel_from_short_name(v); + channels++; } *n_channels = channels; return channels; diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index bad6da9fe..2d9492e95 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -243,7 +243,7 @@ static int position_to_string(struct channel_map *map, char *val, size_t len) uint32_t i, o = 0; int r; o += snprintf(val, len, "[ "); - for (i = 0; i < map->channels; i++) { + for (i = 0; i < map->n_pos; i++) { r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", spa_debug_type_find_short_name(spa_type_audio_channel, map->pos[i])); @@ -1645,7 +1645,7 @@ skip_channels: } else { const struct channel_map *map = NULL; spa_pod_builder_int(b, min); - if (state->default_pos.channels == min) { + if (state->default_pos.n_pos == min) { map = &state->default_pos; spa_log_debug(state->log, "%p: using provided default", state); } else if (min <= 8) { @@ -1655,7 +1655,7 @@ skip_channels: if (map) { spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_position, 0); spa_pod_builder_push_array(b, &f[0]); - for (i = 0; i < map->channels; i++) { + for (i = 0; i < map->n_pos; i++) { spa_log_debug(state->log, "%p: position %zd %d", state, i, map->pos[i]); spa_pod_builder_id(b, map->pos[i]); } diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 09b5140b9..8b8a07721 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -72,7 +72,7 @@ struct buffer { #define BW_PERIOD (3 * SPA_NSEC_PER_SEC) struct channel_map { - uint32_t channels; + uint32_t n_pos; uint32_t pos[MAX_CHANNELS]; }; @@ -315,7 +315,7 @@ void spa_alsa_emit_port_info(struct state *state, bool full); static inline void spa_alsa_parse_position(struct channel_map *map, const char *val, size_t len) { - spa_audio_parse_position_n(val, len, map->pos, SPA_N_ELEMENTS(map->pos), &map->channels); + spa_audio_parse_position_n(val, len, map->pos, SPA_N_ELEMENTS(map->pos), &map->n_pos); } static inline uint32_t spa_alsa_parse_rates(uint32_t *rates, uint32_t max, const char *val, size_t len) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index bb7f24831..ce0765f83 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -232,16 +232,17 @@ struct impl { float *discard_data; }; -static inline void print_channels(char *buffer, size_t max_size, uint32_t n_channels, uint32_t *positions) +static inline void print_channels(char *buffer, size_t max_size, uint32_t n_positions, uint32_t *positions) { uint32_t i; struct spa_strbuf buf; spa_strbuf_init(&buf, buffer, max_size); spa_strbuf_append(&buf, "["); - for (i = 0; i < n_channels; i++) { + for (i = 0; i < n_positions; i++) { spa_strbuf_append(&buf, "%s%s", i ? "," : "", spa_type_audio_channel_to_short_name(positions[i])); + } spa_strbuf_append(&buf, "]"); } From 7177f8269d7df4a063b59dfafe90bd9bc9a44562 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 22 Oct 2025 12:54:30 +0200 Subject: [PATCH 04/31] bluez: use function to get the channel position from a name --- spa/plugins/bluez5/a2dp-codec-opus.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c index 7a330e8e0..0881c0255 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus.c +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -253,14 +253,8 @@ static const struct surround_encoder_mapping surround_encoders[] = { static uint32_t bt_channel_from_name(const char *name) { size_t i; - enum spa_audio_channel position = SPA_AUDIO_CHANNEL_UNKNOWN; + enum spa_audio_channel position = spa_type_audio_channel_from_short_name(name); - for (i = 0; spa_type_audio_channel[i].name; i++) { - if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) { - position = spa_type_audio_channel[i].type; - break; - } - } for (i = 0; i < SPA_N_ELEMENTS(audio_locations); i++) { if (position == audio_locations[i].position) return audio_locations[i].mask; From 11f1298f533bff1a2bc2caa4c0861e5273ed7916 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 22 Oct 2025 13:04:53 +0200 Subject: [PATCH 05/31] spa: make a function to make a channel short name Make a function that can generate and parse a short name for the positions that are not in the type list, like the AUX channels. --- pipewire-alsa/alsa-plugins/pcm_pipewire.c | 3 ++- spa/include/spa/param/audio/raw-types.h | 24 ++++++++++++++++++- spa/plugins/alsa/alsa-pcm.c | 5 ++-- spa/plugins/audioconvert/audioconvert.c | 19 ++++++++------- spa/plugins/avb/avb-pcm.c | 5 ++-- spa/plugins/filter-graph/filter-graph.c | 5 ++-- src/modules/module-jack-tunnel.c | 6 +++-- src/modules/module-netjack2-driver.c | 7 +++--- src/modules/module-netjack2-manager.c | 8 +++---- src/modules/module-protocol-pulse/format.c | 4 ++-- src/modules/module-protocol-pulse/format.h | 2 +- src/modules/module-protocol-pulse/message.c | 4 +++- src/modules/module-protocol-pulse/module.c | 5 ++-- .../modules/module-stream-restore.c | 4 +++- src/modules/module-zeroconf-discover.c | 4 ++-- src/tools/pw-cat.c | 7 +++--- 16 files changed, 74 insertions(+), 38 deletions(-) diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index 27495a296..bf7196898 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -925,11 +925,12 @@ static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io, } for (i = 0; i < map->channels; i++) { uint32_t pos = chmap_to_channel(map->pos[i]); + char buf[8]; if (i < max_position) position[i] = pos; pw_log_debug("map %d: %s / %s", i, snd_pcm_chmap_name(map->pos[i]), - spa_debug_type_find_short_name(spa_type_audio_channel, pos)); + spa_type_audio_channel_make_short_name(pos, buf, sizeof(buf), "UNK")); } return 1; } diff --git a/spa/include/spa/param/audio/raw-types.h b/spa/include/spa/param/audio/raw-types.h index c862999d5..fbaa80abf 100644 --- a/spa/include/spa/param/audio/raw-types.h +++ b/spa/include/spa/param/audio/raw-types.h @@ -267,12 +267,34 @@ static const struct spa_type_info spa_type_audio_channel[] = { SPA_API_AUDIO_RAW_TYPES uint32_t spa_type_audio_channel_from_short_name(const char *name) { - return spa_type_from_short_name(name, spa_type_audio_channel, SPA_AUDIO_CHANNEL_UNKNOWN); + uint32_t res; + if (spa_strstartswith(name, "AUX")) { + if (spa_atou32(name+3, &res, 10) && res < 0x1000) + res = SPA_AUDIO_CHANNEL_AUX0 + res; + else + res = SPA_AUDIO_CHANNEL_UNKNOWN; + } else { + res = spa_type_from_short_name(name, spa_type_audio_channel, SPA_AUDIO_CHANNEL_UNKNOWN); + } + return res; } SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_channel_to_short_name(uint32_t type) { return spa_type_to_short_name(type, spa_type_audio_channel, "UNK"); } +SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_channel_make_short_name(uint32_t type, + char *buf, size_t size, const char *unknown) +{ + if (SPA_AUDIO_CHANNEL_IS_AUX(type)) { + snprintf(buf, size, "AUX%u", type - SPA_AUDIO_CHANNEL_AUX0); + } else { + const char *str = spa_type_to_short_name(type, spa_type_audio_channel, NULL); + if (str == NULL) + return unknown; + snprintf(buf, size, "%.7s", str); + } + return buf; +} #define SPA_TYPE_INFO_AudioVolumeRampScale SPA_TYPE_INFO_ENUM_BASE "AudioVolumeRampScale" #define SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE SPA_TYPE_INFO_AudioVolumeRampScale ":" diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 2d9492e95..676214685 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -241,12 +241,13 @@ static int alsa_set_param(struct state *state, const char *k, const char *s) static int position_to_string(struct channel_map *map, char *val, size_t len) { uint32_t i, o = 0; + char pos[8]; int r; o += snprintf(val, len, "[ "); for (i = 0; i < map->n_pos; i++) { r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", - spa_debug_type_find_short_name(spa_type_audio_channel, - map->pos[i])); + spa_type_audio_channel_make_short_name(map->pos[i], + pos, sizeof(pos), "UNK")); if (r < 0 || o + r >= len) return -ENOSPC; o += r; diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index dd58ee9de..1b4ae18fb 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -424,7 +424,6 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p uint32_t position, bool is_dsp, bool is_monitor, bool is_control) { struct port *port = GET_PORT(this, direction, port_id); - const char *name; spa_assert(port_id < MAX_PORTS); @@ -439,8 +438,7 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p port->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); - name = spa_debug_type_find_short_name(spa_type_audio_channel, position); - snprintf(port->position, sizeof(port->position), "%s", name ? name : "UNK"); + spa_type_audio_channel_make_short_name(position, port->position, sizeof(port->position), "UNK"); port->info = SPA_PORT_INFO_INIT(); port->info.change_mask = port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | @@ -2067,6 +2065,7 @@ static int setup_in_convert(struct impl *this) for (i = 0; i < src_info.info.raw.channels; i++) { for (j = 0; j < dst_info.info.raw.channels; j++) { uint32_t pi, pj; + char b1[8], b2[8]; pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); @@ -2077,8 +2076,8 @@ static int setup_in_convert(struct impl *this) remap = true; spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, i, in->remap[i], j, - spa_debug_type_find_short_name(spa_type_audio_channel, pi), - spa_debug_type_find_short_name(spa_type_audio_channel, pj)); + spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"), + spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK")); spa_format_audio_raw_set_position(&dst_info.info.raw, j, -1); break; } @@ -2192,10 +2191,11 @@ static void set_volume(struct impl *this) static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position, uint32_t max_position) { uint32_t i, idx = 0; + char buf[8]; for (i = 0; i < channels; i++) idx += snprintf(str + idx, len - idx, "%s%s", i == 0 ? "" : " ", - spa_debug_type_find_short_name(spa_type_audio_channel, - position[i % max_position])); + spa_type_audio_channel_make_short_name(position[i % max_position], + buf, sizeof(buf), "UNK")); return str; } @@ -2359,6 +2359,7 @@ static int setup_out_convert(struct impl *this) for (i = 0; i < src_info.info.raw.channels; i++) { for (j = 0; j < dst_info.info.raw.channels; j++) { uint32_t pi, pj; + char b1[8], b2[8]; pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); @@ -2370,8 +2371,8 @@ static int setup_out_convert(struct impl *this) spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, i, out->remap[i], j, - spa_debug_type_find_short_name(spa_type_audio_channel, pi), - spa_debug_type_find_short_name(spa_type_audio_channel, pj)); + spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"), + spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK")); spa_format_audio_raw_set_position(&dst_info.info.raw, j, -1); break; diff --git a/spa/plugins/avb/avb-pcm.c b/spa/plugins/avb/avb-pcm.c index f54e9dabf..0402b8c75 100644 --- a/spa/plugins/avb/avb-pcm.c +++ b/spa/plugins/avb/avb-pcm.c @@ -86,11 +86,12 @@ static int position_to_string(struct channel_map *map, char *val, size_t len) { uint32_t i, o = 0; int r; + char pos[8]; o += snprintf(val, len, "[ "); for (i = 0; i < map->channels; i++) { r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", - spa_debug_type_find_short_name(spa_type_audio_channel, - map->pos[i])); + spa_type_audio_channel_make_short_name(map->pos[i], + pos, sizeof(pos), "UNK")); if (r < 0 || o + r >= len) return -ENOSPC; o += r; diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index ce0765f83..9271ace34 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -236,13 +236,14 @@ static inline void print_channels(char *buffer, size_t max_size, uint32_t n_posi { uint32_t i; struct spa_strbuf buf; + char pos[8]; spa_strbuf_init(&buf, buffer, max_size); spa_strbuf_append(&buf, "["); for (i = 0; i < n_positions; i++) { spa_strbuf_append(&buf, "%s%s", i ? "," : "", - spa_type_audio_channel_to_short_name(positions[i])); - + spa_type_audio_channel_make_short_name(positions[i], + pos, sizeof(pos), "UNK")); } spa_strbuf_append(&buf, "]"); } diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 0e1b3c3fb..4ca51bae8 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -514,6 +514,7 @@ static void make_stream_ports(struct stream *s) for (i = 0; i < s->n_ports; i++) { struct port *port = s->ports[i]; char *link_port = NULL; + char pos[8]; if (port != NULL) { s->ports[i] = NULL; @@ -523,8 +524,9 @@ static void make_stream_ports(struct stream *s) } if (i < s->info.channels) { - str = spa_debug_type_find_short_name(spa_type_audio_channel, - spa_format_audio_raw_get_position(&s->info, i)); + str = spa_type_audio_channel_make_short_name( + spa_format_audio_raw_get_position(&s->info, i), + pos, sizeof(pos), NULL); if (str) snprintf(name, sizeof(name), "%s_%s", prefix, str); else diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index 5e3149b41..b71284841 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -441,11 +441,12 @@ static void make_stream_ports(struct stream *s) } if (i < s->info.channels) { - str = spa_debug_type_find_short_name(spa_type_audio_channel, - spa_format_audio_raw_get_position(&s->info, i)); + str = spa_type_audio_channel_make_short_name( + spa_format_audio_raw_get_position(&s->info, i), + name, sizeof(name), "UNK"); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", - PW_KEY_AUDIO_CHANNEL, str ? str : "UNK", + PW_KEY_AUDIO_CHANNEL, str, PW_KEY_PORT_PHYSICAL, "true", NULL); diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 454632c8f..e171b2785 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -601,12 +601,12 @@ static void make_stream_ports(struct stream *s) } if (i < s->info.channels) { - str = spa_debug_type_find_short_name(spa_type_audio_channel, - spa_format_audio_raw_get_position(&s->info, i)); - + str = spa_type_audio_channel_make_short_name( + spa_format_audio_raw_get_position(&s->info, i), + name, sizeof(name), "UNK"); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", - PW_KEY_AUDIO_CHANNEL, str ? str : "UNK", + PW_KEY_AUDIO_CHANNEL, str, PW_KEY_PORT_PHYSICAL, "true", NULL); diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index f92e29e78..b34765641 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -298,9 +298,9 @@ uint32_t channel_pa2id(enum channel_position channel) return audio_channels[channel].channel; } -const char *channel_id2name(uint32_t channel) +const char *channel_id2name(uint32_t channel, char *buf, size_t size) { - return spa_type_audio_channel_to_short_name(channel); + return spa_type_audio_channel_make_short_name(channel, buf, size, "UNK"); } uint32_t channel_name2id(const char *name) diff --git a/src/modules/module-protocol-pulse/format.h b/src/modules/module-protocol-pulse/format.h index 41cfb4f9d..50bb8e2f8 100644 --- a/src/modules/module-protocol-pulse/format.h +++ b/src/modules/module-protocol-pulse/format.h @@ -190,7 +190,7 @@ void sample_spec_fix(struct sample_spec *ss, struct channel_map *map, struct spa_dict *props); uint32_t channel_pa2id(enum channel_position channel); -const char *channel_id2name(uint32_t channel); +const char *channel_id2name(uint32_t channel, char *buf, size_t size); uint32_t channel_name2id(const char *name); enum channel_position channel_id2pa(uint32_t id, uint32_t *aux); const char *channel_id2paname(uint32_t id, uint32_t *aux); diff --git a/src/modules/module-protocol-pulse/message.c b/src/modules/module-protocol-pulse/message.c index dbebc437e..19b4f82d6 100644 --- a/src/modules/module-protocol-pulse/message.c +++ b/src/modules/module-protocol-pulse/message.c @@ -761,11 +761,13 @@ int message_dump(enum spa_log_level level, const char *prefix, struct message *m case TAG_CHANNEL_MAP: { struct channel_map map; + char pos[8]; if ((res = read_channel_map(m, &map)) < 0) return res; pw_log(level, "%s %u: channelmap: channels:%u", prefix, o, map.channels); for (i = 0; i < map.channels; i++) - pw_log(level, "%s %d: %s", prefix, i, channel_id2name(map.map[i])); + pw_log(level, "%s %d: %s", prefix, i, + channel_id2name(map.map[i], pos, sizeof(pos))); break; } case TAG_CVOLUME: diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c index 58bcce52b..fd997883e 100644 --- a/src/modules/module-protocol-pulse/module.c +++ b/src/modules/module-protocol-pulse/module.c @@ -283,14 +283,15 @@ void audioinfo_to_properties(struct spa_audio_info_raw *info, struct pw_properti if (info->rate) pw_properties_setf(props, SPA_KEY_AUDIO_RATE, "%u", info->rate); if (info->channels) { - char *s, *p; + char *s, *p, pos[8]; pw_properties_setf(props, SPA_KEY_AUDIO_CHANNELS, "%u", info->channels); p = s = alloca(info->channels * 8); for (i = 0; i < info->channels; i++) p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ", ", - channel_id2name(spa_format_audio_raw_get_position(info, i))); + channel_id2name(spa_format_audio_raw_get_position(info, i), + pos, sizeof(pos))); pw_properties_setf(props, SPA_KEY_AUDIO_POSITION, "[ %s ]", s); } } diff --git a/src/modules/module-protocol-pulse/modules/module-stream-restore.c b/src/modules/module-protocol-pulse/modules/module-stream-restore.c index bdb9cfd7d..a33ab6fa2 100644 --- a/src/modules/module-protocol-pulse/modules/module-stream-restore.c +++ b/src/modules/module-protocol-pulse/modules/module-stream-restore.c @@ -295,9 +295,11 @@ static int do_extension_stream_restore_write(struct module *module, struct clien fprintf(f, " ]"); } if (map.channels > 0) { + char pos[8]; fprintf(f, ", \"channels\": ["); for (i = 0; i < map.channels; i++) - fprintf(f, "%s\"%s\"", (i == 0 ? " ":", "), channel_id2name(map.map[i])); + fprintf(f, "%s\"%s\"", (i == 0 ? " ":", "), + channel_id2name(map.map[i], pos, sizeof(pos))); fprintf(f, " ]"); } if (device_name != NULL && device_name[0] && diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index 35e48279b..b324db403 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -188,7 +188,7 @@ static void pw_properties_from_avahi_string(const char *key, const char *value, else if (spa_streq(key, "channel_map")) { struct channel_map channel_map; uint32_t i, pos[CHANNELS_MAX]; - char *p, *s; + char *p, *s, buf[8]; spa_zero(channel_map); channel_map_parse(value, &channel_map); @@ -198,7 +198,7 @@ static void pw_properties_from_avahi_string(const char *key, const char *value, p += spa_scnprintf(p, 2, "["); for (i = 0; i < channel_map.channels; i++) p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ",", - channel_id2name(pos[i])); + channel_id2name(pos[i], buf, sizeof(buf))); p += spa_scnprintf(p, 2, "]"); pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s); } diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 52789d421..8210addb6 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -747,10 +747,11 @@ static int channelmap_default(struct channelmap *map, int n_channels) static void channelmap_print(struct channelmap *map) { uint32_t i; - + char pos[8]; for (i = 0; i < map->n_channels; i++) { - const char *name = spa_type_audio_channel_to_short_name(map->channels[i]); - fprintf(stderr, "%s%s", name, i + 1 < map->n_channels ? "," : ""); + fprintf(stderr, "%s%s", i ? "," : "", + spa_type_audio_channel_make_short_name(map->channels[i], + pos, sizeof(pos), "UNK")); } } From c5533b3c32775dc52c54c6d4a33531d2f03c36e1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 22 Oct 2025 13:26:52 +0200 Subject: [PATCH 06/31] spa: add all channel positions to the params Pass all the positions of all channels to the format param, even when there are more channels then positions. --- spa/include/spa/param/audio/raw-utils.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 8a9542c2e..7c0a92776 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -80,8 +80,8 @@ SPA_API_AUDIO_RAW_UTILS struct spa_pod * spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_raw *info) { - struct spa_pod_frame f; - spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); + struct spa_pod_frame f[2]; + spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), @@ -96,13 +96,16 @@ spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { - spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position, - SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, - SPA_MIN(info->channels, SPA_N_ELEMENTS(info->position)), - info->position), 0); + uint32_t i; + spa_pod_builder_prop(builder, SPA_FORMAT_AUDIO_position, 0); + spa_pod_builder_push_array(builder, &f[1]); + for (i = 0; i < info->channels; i++) + spa_pod_builder_id(builder, + spa_format_audio_raw_get_position(info, i)); + spa_pod_builder_pop(builder, &f[1]); } } - return (struct spa_pod*)spa_pod_builder_pop(builder, &f); + return (struct spa_pod*)spa_pod_builder_pop(builder, &f[0]); } /** From b8eeb2db45adfb4fa6df2f4c675c700d6b32570e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 23 Oct 2025 14:37:46 +0200 Subject: [PATCH 07/31] spa: make it possible to extend the spa_audio_info struct Add functions that take the size of the spa_audio_info struct in various functions. We can use this to determine how many channels and channel positions we can store. Error out if we try to use more channels than we can fit positions. This is probably the safest thing to do because most code will blindly try to get the positions without checking the channel count. Make sure we also propagate errors to the callers. --- spa/include/spa/param/audio/format-utils.h | 23 ++++-- spa/include/spa/param/audio/raw-json.h | 89 ++++++++++++++++------ spa/include/spa/param/audio/raw-utils.h | 48 ++++++++---- spa/include/spa/param/audio/raw.h | 10 ++- 4 files changed, 124 insertions(+), 46 deletions(-) diff --git a/spa/include/spa/param/audio/format-utils.h b/spa/include/spa/param/audio/format-utils.h index d608a4bdf..07f5d94e3 100644 --- a/spa/include/spa/param/audio/format-utils.h +++ b/spa/include/spa/param/audio/format-utils.h @@ -47,7 +47,7 @@ extern "C" { #endif SPA_API_AUDIO_FORMAT_UTILS int -spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info) +spa_format_audio_ext_parse(const struct spa_pod *format, struct spa_audio_info *info, size_t size) { int res; @@ -59,7 +59,7 @@ spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - return spa_format_audio_raw_parse(format, &info->info.raw); + return spa_format_audio_raw_ext_parse(format, &info->info.raw, size); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_audio_dsp_parse(format, &info->info.dsp); case SPA_MEDIA_SUBTYPE_iec958: @@ -98,13 +98,19 @@ spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info return -ENOTSUP; } +SPA_API_AUDIO_FORMAT_UTILS int +spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info) +{ + return spa_format_audio_ext_parse(format, info, sizeof(*info)); +} + SPA_API_AUDIO_FORMAT_UTILS struct spa_pod * -spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id, - const struct spa_audio_info *info) +spa_format_audio_ext_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info *info, size_t size) { switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - return spa_format_audio_raw_build(builder, id, &info->info.raw); + return spa_format_audio_raw_ext_build(builder, id, &info->info.raw, size); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_audio_dsp_build(builder, id, &info->info.dsp); case SPA_MEDIA_SUBTYPE_iec958: @@ -143,6 +149,13 @@ spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id, errno = ENOTSUP; return NULL; } + +SPA_API_AUDIO_FORMAT_UTILS struct spa_pod * +spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info *info) +{ + return spa_format_audio_ext_build(builder, id, info, sizeof(*info)); +} /** * \} */ diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index 7ef1b27f3..7c8c11ca3 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -46,6 +46,7 @@ spa_audio_parse_position_n(const char *str, size_t len, *n_channels = channels; return channels; } + SPA_API_AUDIO_RAW_JSON int spa_audio_parse_position(const char *str, size_t len, uint32_t *position, uint32_t *n_channels) @@ -54,9 +55,11 @@ spa_audio_parse_position(const char *str, size_t len, } SPA_API_AUDIO_RAW_JSON int -spa_audio_info_raw_update(struct spa_audio_info_raw *info, const char *key, const char *val, bool force) +spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, + const char *key, const char *val, bool force) { uint32_t v; + uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); if (spa_streq(key, SPA_KEY_AUDIO_FORMAT)) { if (force || info->format == 0) info->format = (enum spa_audio_format)spa_type_audio_format_from_short_name(val); @@ -64,42 +67,84 @@ spa_audio_info_raw_update(struct spa_audio_info_raw *info, const char *key, cons if (spa_atou32(val, &v, 0) && (force || info->rate == 0)) info->rate = v; } else if (spa_streq(key, SPA_KEY_AUDIO_CHANNELS)) { - if (spa_atou32(val, &v, 0) && (force || info->channels == 0)) + if (spa_atou32(val, &v, 0) && (force || info->channels == 0)) { + if (v > max_position) + return -ECHRNG; info->channels = v; + } } else if (spa_streq(key, SPA_KEY_AUDIO_POSITION)) { if (force || info->channels == 0) { if (spa_audio_parse_position_n(val, strlen(val), info->position, - SPA_N_ELEMENTS(info->position), &info->channels) > 0) + max_position, &v) > 0) { + if (v > max_position) + return -ECHRNG; SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); + } } } return 0; } +SPA_API_AUDIO_RAW_JSON int +spa_audio_info_raw_update(struct spa_audio_info_raw *info, + const char *key, const char *val, bool force) +{ + return spa_audio_info_raw_ext_update(info, sizeof(*info), key, val, force); +} + +SPA_API_AUDIO_RAW_JSON int +spa_audio_info_raw_ext_init_dict_keys_va(struct spa_audio_info_raw *info, size_t size, + const struct spa_dict *defaults, + const struct spa_dict *dict, va_list args) +{ + int res; + + memset(info, 0, size); + SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); + if (dict) { + const char *val, *key; + while ((key = va_arg(args, const char *))) { + if ((val = spa_dict_lookup(dict, key)) == NULL) + continue; + if ((res = spa_audio_info_raw_ext_update(info, size, + key, val, true)) < 0) + return res; + } + } + if (defaults) { + const struct spa_dict_item *it; + spa_dict_for_each(it, defaults) + if ((res = spa_audio_info_raw_ext_update(info, size, + it->key, it->value, false)) < 0) + return res; + } + return 0; +} + +SPA_API_AUDIO_RAW_JSON int SPA_SENTINEL +spa_audio_info_raw_ext_init_dict_keys(struct spa_audio_info_raw *info, size_t size, + const struct spa_dict *defaults, + const struct spa_dict *dict, ...) +{ + va_list args; + int res; + va_start(args, dict); + res = spa_audio_info_raw_ext_init_dict_keys_va(info, size, defaults, dict, args); + va_end(args); + return res; +} + SPA_API_AUDIO_RAW_JSON int SPA_SENTINEL spa_audio_info_raw_init_dict_keys(struct spa_audio_info_raw *info, const struct spa_dict *defaults, const struct spa_dict *dict, ...) { - spa_zero(*info); - SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); - if (dict) { - const char *val, *key; - va_list args; - va_start(args, dict); - while ((key = va_arg(args, const char *))) { - if ((val = spa_dict_lookup(dict, key)) == NULL) - continue; - spa_audio_info_raw_update(info, key, val, true); - } - va_end(args); - } - if (defaults) { - const struct spa_dict_item *it; - spa_dict_for_each(it, defaults) - spa_audio_info_raw_update(info, it->key, it->value, false); - } - return 0; + va_list args; + int res; + va_start(args, dict); + res = spa_audio_info_raw_ext_init_dict_keys_va(info, sizeof(*info), defaults, dict, args); + va_end(args); + return res; } /** diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 7c0a92776..551f47af1 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -58,10 +58,12 @@ spa_format_audio_raw_copy_positions(const struct spa_audio_info_raw *info, uint3 } SPA_API_AUDIO_RAW_UTILS int -spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info) +spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_info_raw *info, size_t size) { struct spa_pod *position = NULL; int res; + uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + info->flags = 0; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, @@ -69,19 +71,29 @@ spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_r SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); + if (info->channels > max_position) + return -ENOTSUP; if (position == NULL || - !spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_N_ELEMENTS(info->position))) + spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); return res; } -SPA_API_AUDIO_RAW_UTILS struct spa_pod * -spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, - const struct spa_audio_info_raw *info) +SPA_API_AUDIO_RAW_UTILS int +spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info) { - struct spa_pod_frame f[2]; - spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, id); + return spa_format_audio_raw_ext_parse(format, info, sizeof(*info)); +} + +SPA_API_AUDIO_RAW_UTILS struct spa_pod * +spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info_raw *info, size_t size) +{ + struct spa_pod_frame f; + uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), @@ -95,17 +107,21 @@ spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, if (info->channels != 0) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); - if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { - uint32_t i; - spa_pod_builder_prop(builder, SPA_FORMAT_AUDIO_position, 0); - spa_pod_builder_push_array(builder, &f[1]); - for (i = 0; i < info->channels; i++) - spa_pod_builder_id(builder, - spa_format_audio_raw_get_position(info, i)); - spa_pod_builder_pop(builder, &f[1]); + if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED) && + max_position > info->channels) { + spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position, + SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, + info->channels, info->position), 0); } } - return (struct spa_pod*)spa_pod_builder_pop(builder, &f[0]); + return (struct spa_pod*)spa_pod_builder_pop(builder, &f); +} + +SPA_API_AUDIO_RAW_UTILS struct spa_pod * +spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info_raw *info) +{ + return spa_format_audio_raw_ext_build(builder, id, info, sizeof(*info)); } /** diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 5b57a5bf1..1688595e1 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -284,14 +284,18 @@ struct spa_audio_info_raw { enum spa_audio_format format; /*< format, one of enum spa_audio_format */ uint32_t flags; /*< extra flags */ uint32_t rate; /*< sample rate */ - uint32_t channels; /*< number of channels. This can be larger than - * SPA_AUDIO_MAX_POSITION, the position is taken - * (index % SPA_AUDIO_MAX_POSITION) */ + uint32_t channels; /*< number of channels. This can be more than SPA_AUDIO_MAX_POSITION + * and you may assume there is enough padding for the extra + * channel positions. */ uint32_t position[SPA_AUDIO_MAX_POSITION]; /*< channel position from enum spa_audio_channel */ + /* more channels can be added here */ }; #define SPA_AUDIO_INFO_RAW_INIT(...) ((struct spa_audio_info_raw) { __VA_ARGS__ }) +#define SPA_AUDIO_INFO_RAW_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_info_raw,position))/sizeof(uint32_t)) + + #define SPA_KEY_AUDIO_FORMAT "audio.format" /**< an audio format as string, * Ex. "S16LE" */ #define SPA_KEY_AUDIO_CHANNEL "audio.channel" /**< an audio channel as string, From 5e1e3fca1ebf284e3d37a2e87fd1c866baed6bc1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 23 Oct 2025 18:01:35 +0200 Subject: [PATCH 08/31] modules: handle format parsing errors --- src/modules/module-combine-stream.c | 9 ++++++--- src/modules/module-echo-cancel.c | 9 ++++++--- src/modules/module-example-filter.c | 11 +++++++---- src/modules/module-example-sink.c | 9 ++++++--- src/modules/module-example-source.c | 9 ++++++--- src/modules/module-ffado-driver.c | 11 +++++++---- src/modules/module-filter-chain.c | 11 +++++++---- src/modules/module-jack-tunnel.c | 11 +++++++---- src/modules/module-loopback.c | 13 ++++++++----- src/modules/module-netjack2-driver.c | 11 +++++++---- src/modules/module-netjack2-manager.c | 13 ++++++++----- src/modules/module-pipe-tunnel.c | 9 ++++++--- src/modules/module-protocol-simple.c | 18 +++++++++++------- src/modules/module-pulse-tunnel.c | 9 ++++++--- src/modules/module-rtp/stream.c | 14 ++++++++++---- src/modules/module-snapcast-discover.c | 15 +++++++++++---- src/modules/module-vban/stream.c | 9 ++++++--- 17 files changed, 125 insertions(+), 66 deletions(-) diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index a9dec8c2b..a9833d68f 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -326,9 +326,9 @@ struct stream { unsigned int have_latency:1; }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), @@ -1638,7 +1638,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(props, impl->combine_props, "resample.prefill"); copy_props(props, impl->combine_props, "resample.disable"); - parse_audio_info(impl->combine_props, &impl->info); + if ((res = parse_audio_info(impl->combine_props, &impl->info)) < 0) { + pw_log_error( "can't create format: %s", spa_strerror(res)); + goto error; + } copy_props(props, impl->stream_props, PW_KEY_NODE_LOOP_NAME); copy_props(props, impl->stream_props, PW_KEY_NODE_GROUP); diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 0d640f162..997506e6f 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -1203,9 +1203,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -1291,7 +1291,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(props, "resample.prefill") == NULL) pw_properties_set(props, "resample.prefill", "true"); - parse_audio_info(props, &info); + if ((res = parse_audio_info(props, &info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->capture_info = info; impl->source_info = info; diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index 6dcb155cc..60b6dda95 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -467,9 +467,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), @@ -573,8 +573,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str); - parse_audio_info(impl->capture_props, &impl->capture_info); - parse_audio_info(impl->playback_props, &impl->playback_info); + if ((res = parse_audio_info(impl->capture_props, &impl->capture_info)) < 0 || + (res = parse_audio_info(impl->playback_props, &impl->playback_info)) < 0) { + pw_log_error( "can't parse formats: %s", spa_strerror(res)); + goto error; + } if (!impl->capture_info.rate && !impl->playback_info.rate) { if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) diff --git a/src/modules/module-example-sink.c b/src/modules/module-example-sink.c index b44470f68..00b880175 100644 --- a/src/modules/module-example-sink.c +++ b/src/modules/module-example-sink.c @@ -273,9 +273,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -395,7 +395,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_CLASS); - parse_audio_info(impl->stream_props, &impl->info); + if ((res = parse_audio_info(impl->stream_props, &impl->info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { diff --git a/src/modules/module-example-source.c b/src/modules/module-example-source.c index f04ae1afc..20f296a17 100644 --- a/src/modules/module-example-source.c +++ b/src/modules/module-example-source.c @@ -279,9 +279,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -401,7 +401,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_CLASS); - parse_audio_info(impl->stream_props, &impl->info); + if ((res = parse_audio_info(impl->stream_props, &impl->info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index c71639b3b..de5ed0ff0 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -1429,9 +1429,9 @@ static void parse_devices(struct impl *impl, const char *val, size_t len) } } -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), @@ -1582,8 +1582,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_NODE_PAUSE_ON_IDLE); - parse_audio_info(impl->source.props, &impl->source.info); - parse_audio_info(impl->sink.props, &impl->sink.info); + if ((res = parse_audio_info(impl->source.props, &impl->source.info)) < 0 || + (res = parse_audio_info(impl->sink.props, &impl->sink.info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index d5b15d4ab..6b759434b 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1833,9 +1833,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, @@ -1928,8 +1928,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_MEDIA_NAME); copy_props(impl, props, "resample.prefill"); - parse_audio_info(impl->capture_props, &impl->capture_info); - parse_audio_info(impl->playback_props, &impl->playback_info); + if ((res = parse_audio_info(impl->capture_props, &impl->capture_info)) < 0 || + (res = parse_audio_info(impl->playback_props, &impl->playback_info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } if (!impl->capture_info.rate && !impl->playback_info.rate) { if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 4ca51bae8..8e8065c26 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -1053,9 +1053,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), @@ -1178,8 +1178,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, "jack.connect-audio"); copy_props(impl, props, "jack.connect-midi"); - parse_audio_info(impl->source.props, &impl->source.info); - parse_audio_info(impl->sink.props, &impl->sink.info); + if ((res = parse_audio_info(impl->source.props, &impl->source.info)) < 0 || + (res = parse_audio_info(impl->sink.props, &impl->sink.info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->source.n_midi = pw_properties_get_uint32(impl->source.props, "midi.ports", DEFAULT_MIDI_PORTS); diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 64022b69d..976a8c7a4 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -838,9 +838,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, @@ -952,9 +952,12 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str); - parse_audio_info(props, &impl->info); - parse_audio_info(impl->capture_props, &impl->capture_info); - parse_audio_info(impl->playback_props, &impl->playback_info); + if ((res = parse_audio_info(props, &impl->info)) < 0 || + (res = parse_audio_info(impl->capture_props, &impl->capture_info)) < 0 || + (res = parse_audio_info(impl->playback_props, &impl->playback_info)) < 0) { + pw_log_error( "can't parse formats: %s", spa_strerror(res)); + goto error; + } if (!impl->capture_info.rate && !impl->playback_info.rate) { if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index b71284841..e98ac9584 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -1221,9 +1221,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, @@ -1339,8 +1339,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, "midi.ports"); copy_props(impl, props, "audio.ports"); - parse_audio_info(impl->source.props, &impl->source.info); - parse_audio_info(impl->sink.props, &impl->sink.info); + if ((res = parse_audio_info(impl->source.props, &impl->source.info)) < 0 || + (res = parse_audio_info(impl->sink.props, &impl->sink.info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->source.wanted_n_midi = pw_properties_get_int32(impl->source.props, "midi.ports", DEFAULT_MIDI_PORTS); diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index e171b2785..4f3a1cf73 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -181,7 +181,7 @@ static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info); +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info); struct port { enum spa_direction direction; @@ -969,8 +969,11 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param follower->sink.direction = PW_DIRECTION_INPUT; follower->sink.props = pw_properties_copy(impl->sink_props); - parse_audio_info(follower->source.props, &follower->source.info); - parse_audio_info(follower->sink.props, &follower->sink.info); + if ((res = parse_audio_info(follower->source.props, &follower->source.info)) < 0 || + (res = parse_audio_info(follower->sink.props, &follower->sink.info)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + return res; + } follower->source.n_audio = pw_properties_get_uint32(follower->source.props, "audio.ports", follower->source.info.channels ? @@ -1292,9 +1295,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, diff --git a/src/modules/module-pipe-tunnel.c b/src/modules/module-pipe-tunnel.c index 9798336df..2f66a9ed7 100644 --- a/src/modules/module-pipe-tunnel.c +++ b/src/modules/module-pipe-tunnel.c @@ -745,9 +745,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -896,7 +896,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_TARGET_OBJECT); copy_props(impl, props, "pipe.filename"); - parse_audio_info(impl->stream_props, &impl->info); + if ((res = parse_audio_info(impl->stream_props, &impl->info)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + goto error; + } impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { diff --git a/src/modules/module-protocol-simple.c b/src/modules/module-protocol-simple.c index 6fdef15b8..e1847577a 100644 --- a/src/modules/module-protocol-simple.c +++ b/src/modules/module-protocol-simple.c @@ -815,13 +815,14 @@ static int calc_frame_size(struct spa_audio_info_raw *info) case SPA_AUDIO_FORMAT_F64_OE: return res * 8; default: - return 0; + return -ENOTSUP; } } static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + int res; + if ((res = spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -830,7 +831,8 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, - SPA_KEY_AUDIO_POSITION, NULL); + SPA_KEY_AUDIO_POSITION, NULL)) < 0) + return res; return calc_frame_size(info); } @@ -851,6 +853,7 @@ static int parse_params(struct impl *impl) const char *str; struct spa_json it[1]; char value[512]; + int res; pw_properties_fetch_bool(impl->props, "capture", &impl->capture); pw_properties_fetch_bool(impl->props, "playback", &impl->playback); @@ -894,19 +897,20 @@ static int parse_params(struct impl *impl) copy_props(impl, PW_KEY_NODE_VIRTUAL); copy_props(impl, PW_KEY_NODE_NETWORK); - impl->capture_frame_size = parse_audio_info(impl->capture_props, &impl->capture_info); - if (impl->capture_frame_size == 0) { + if ((res = parse_audio_info(impl->capture_props, &impl->capture_info)) <= 0) { pw_log_error("unsupported capture audio format:%d channels:%d", impl->capture_info.format, impl->capture_info.channels); return -EINVAL; } + impl->capture_frame_size = res; - impl->playback_frame_size = parse_audio_info(impl->playback_props, &impl->playback_info); - if (impl->playback_frame_size == 0) { + if ((res = parse_audio_info(impl->playback_props, &impl->playback_info)) <= 0) { pw_log_error("unsupported playback audio format:%d channels:%d", impl->playback_info.format, impl->playback_info.channels); return -EINVAL; } + impl->playback_frame_size = res; + if (impl->capture_info.rate != 0 && pw_properties_get(impl->capture_props, PW_KEY_NODE_RATE) == NULL) pw_properties_setf(impl->capture_props, PW_KEY_NODE_RATE, diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index 8849f7190..d613fb13b 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -1048,9 +1048,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -1192,7 +1192,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_NETWORK); copy_props(impl, props, PW_KEY_MEDIA_CLASS); - parse_audio_info(impl->stream_props, &impl->info); + if ((res = parse_audio_info(impl->stream_props, &impl->info)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + goto error; + } impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index f5ef5188b..3834206ec 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -569,9 +569,9 @@ static const struct format_info *find_audio_format_info(const struct spa_audio_i return NULL; } -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -675,7 +675,10 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, switch (impl->info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - parse_audio_info(props, &impl->info.info.raw); + if ((res = parse_audio_info(props, &impl->info.info.raw)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + goto out; + } impl->stream_info = impl->info; impl->format_info = find_audio_format_info(&impl->info); if (impl->format_info == NULL) { @@ -704,7 +707,10 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, case SPA_MEDIA_SUBTYPE_opus: impl->stream_info.media_type = SPA_MEDIA_TYPE_audio; impl->stream_info.media_subtype = SPA_MEDIA_SUBTYPE_raw; - parse_audio_info(props, &impl->stream_info.info.raw); + if ((res = parse_audio_info(props, &impl->stream_info.info.raw)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + goto out; + } impl->stream_info.info.raw.format = SPA_AUDIO_FORMAT_F32; impl->info.info.opus.rate = impl->stream_info.info.raw.rate; impl->info.info.opus.channels = impl->stream_info.info.raw.channels; diff --git a/src/modules/module-snapcast-discover.c b/src/modules/module-snapcast-discover.c index c4f7c6580..ec4eb3e25 100644 --- a/src/modules/module-snapcast-discover.c +++ b/src/modules/module-snapcast-discover.c @@ -503,9 +503,11 @@ static int add_snapcast_stream(struct impl *impl, struct tunnel *t, return -ENOENT; } -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + int res; + + if ((res = spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -514,12 +516,14 @@ static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_ SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, - SPA_KEY_AUDIO_POSITION, NULL); + SPA_KEY_AUDIO_POSITION, NULL)) < 0) + return res; pw_properties_set(props, PW_KEY_AUDIO_FORMAT, spa_type_audio_format_to_short_name(info->format)); pw_properties_setf(props, PW_KEY_AUDIO_RATE, "%d", info->rate); pw_properties_setf(props, PW_KEY_AUDIO_CHANNELS, "%d", info->channels); + return res; } static int create_stream(struct impl *impl, struct pw_properties *props, @@ -545,7 +549,10 @@ static int create_stream(struct impl *impl, struct pw_properties *props, if ((str = pw_properties_get(props, "capture.props")) == NULL) pw_properties_set(props, "capture.props", "{ media.class = Audio/Sink }"); - parse_audio_info(props, &t->audio_info); + if ((res = parse_audio_info(props, &t->audio_info)) < 0) { + pw_log_error("Can't parse format: %s", spa_strerror(res)); + goto done; + } if ((f = open_memstream(&args, &size)) == NULL) { res = -errno; diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index 4ae0eacd1..22ef8bea5 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -185,9 +185,9 @@ static const struct format_info *find_audio_format_info(const struct spa_audio_i return NULL; } -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -274,7 +274,10 @@ struct vban_stream *vban_stream_new(struct pw_core *core, switch (impl->info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - parse_audio_info(props, &impl->info.info.raw); + if ((res = parse_audio_info(props, &impl->info.info.raw)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + goto out; + } if (SPA_FLAG_IS_SET(impl->info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED)) default_layout(impl->info.info.raw.channels, impl->info.info.raw.position, From be29ae4ef60736dd5c67558ebc92a75a7c0f0c05 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 23 Oct 2025 18:05:22 +0200 Subject: [PATCH 09/31] audioadapter: add some more debug info when parsing fails --- spa/plugins/audioconvert/audioadapter.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 01e94cf58..0d0d3e15f 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -808,6 +808,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if (spa_format_audio_parse(param, &info) < 0) { spa_log_error(this->log, "%p: cannot set Format param: " "parsing the POD failed", this); + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, param); return -EINVAL; } if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) { @@ -841,6 +842,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) { spa_log_error(this->log, "%p: cannot set PortConfig param: " "parsing the POD failed", this); + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, param); return -EINVAL; } @@ -848,8 +850,12 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, struct spa_audio_info info; spa_zero(info); - if ((res = spa_format_audio_parse(format, &info)) < 0) + if ((res = spa_format_audio_parse(format, &info)) < 0) { + spa_log_error(this->log, "%p: cannot set PortConfig param: " + "parsing format failed: %s", this, spa_strerror(res)); + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, format); return res; + } if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw) { info.info.raw.rate = 0; From 6d74eee874ac92d587050218bf7db95fefa4ef6a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 08:53:21 +0200 Subject: [PATCH 10/31] spa: bump channels to 128 again Remove the SPA_AUDIO_MAX_POSITION define and use the SPA_AUDIO_MAX_CHANNELS again. Make a compile time define to override the default max channels of 64. Make sure we compile the SPA library with the default 64 channels. If you use the SPA library on a spa_audio_info you will get 64 channel support, like before. If you want more channels, you will need to make a padded structure or redefine the MAX_CHANNELS before you use the spa_audio_info structures. You can use the padded structure with the new functions that take the structure size. With the extra checks in the parsing code, we avoid making a valid spa_audio_info with too many channels that don't fit in the structure. This means that code that receives a spa_audio_info can assume there is enough padding for all the channels. --- meson.build | 1 + spa/include/spa/param/audio/dsd.h | 2 +- spa/include/spa/param/audio/layout.h | 2 +- spa/include/spa/param/audio/raw-json.h | 2 +- spa/include/spa/param/audio/raw.h | 20 +++++++++----------- spa/lib/lib.c | 8 ++------ 6 files changed, 15 insertions(+), 20 deletions(-) diff --git a/meson.build b/meson.build index e0a333a8f..e81ce8214 100644 --- a/meson.build +++ b/meson.build @@ -115,6 +115,7 @@ cc_flags = common_flags + [ '-Werror=old-style-definition', '-Werror=missing-parameter-type', '-Werror=strict-prototypes', + '-DSPA_AUDIO_MAX_CHANNELS=128u', ] add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c') diff --git a/spa/include/spa/param/audio/dsd.h b/spa/include/spa/param/audio/dsd.h index 733823780..73f3d4e8b 100644 --- a/spa/include/spa/param/audio/dsd.h +++ b/spa/include/spa/param/audio/dsd.h @@ -45,7 +45,7 @@ struct spa_audio_info_dsd { int32_t interleave; /*< interleave bytes */ uint32_t rate; /*< sample rate (in bytes per second) */ uint32_t channels; /*< channels */ - uint32_t position[SPA_AUDIO_MAX_POSITION]; /*< channel position from enum spa_audio_channel */ + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ }; #define SPA_AUDIO_INFO_DSD_INIT(...) ((struct spa_audio_info_dsd) { __VA_ARGS__ }) diff --git a/spa/include/spa/param/audio/layout.h b/spa/include/spa/param/audio/layout.h index e5e59caf9..19e3a3c42 100644 --- a/spa/include/spa/param/audio/layout.h +++ b/spa/include/spa/param/audio/layout.h @@ -20,7 +20,7 @@ extern "C" { struct spa_audio_layout_info { uint32_t n_channels; - uint32_t position[SPA_AUDIO_MAX_POSITION]; + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; }; #define SPA_AUDIO_LAYOUT_Mono 1, { SPA_AUDIO_CHANNEL_MONO, } diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index 7c8c11ca3..c33a9310f 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -51,7 +51,7 @@ SPA_API_AUDIO_RAW_JSON int spa_audio_parse_position(const char *str, size_t len, uint32_t *position, uint32_t *n_channels) { - return spa_audio_parse_position_n(str, len, position, SPA_AUDIO_MAX_POSITION, n_channels); + return spa_audio_parse_position_n(str, len, position, SPA_AUDIO_MAX_CHANNELS, n_channels); } SPA_API_AUDIO_RAW_JSON int diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 1688595e1..392b65daa 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -18,14 +18,10 @@ extern "C" { * \{ */ -/* This is the max number of position info, changing this will change the - * ABI */ -#define SPA_AUDIO_MAX_POSITION 64u - -/* The suggested number of max channels, can be a compile time constant and - * does not affect ABI or API */ +/* This is the max number of channels, changing this will change the + * size of some helper structures. This value should be at least 64 */ #ifndef SPA_AUDIO_MAX_CHANNELS -#define SPA_AUDIO_MAX_CHANNELS 128u +#define SPA_AUDIO_MAX_CHANNELS 64u #endif enum spa_audio_format { @@ -279,16 +275,18 @@ enum spa_audio_volume_ramp_scale { #define SPA_AUDIO_FLAG_NONE (0) /*< no valid flag */ #define SPA_AUDIO_FLAG_UNPOSITIONED (1 << 0) /*< the position array explicitly * contains unpositioned channels. */ -/** Audio information description */ +/** Audio information description. You can assume when you receive this structure + * that there is enought padding to accomodate all channel positions in case the + * channel count is more than SPA_AUDIO_MAX_CHANNELS. */ struct spa_audio_info_raw { enum spa_audio_format format; /*< format, one of enum spa_audio_format */ uint32_t flags; /*< extra flags */ uint32_t rate; /*< sample rate */ - uint32_t channels; /*< number of channels. This can be more than SPA_AUDIO_MAX_POSITION + uint32_t channels; /*< number of channels. This can be more than SPA_AUDIO_MAX_CHANNELS * and you may assume there is enough padding for the extra * channel positions. */ - uint32_t position[SPA_AUDIO_MAX_POSITION]; /*< channel position from enum spa_audio_channel */ - /* more channels can be added here */ + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ + /* padding follows here when channels > SPA_AUDIO_MAX_CHANNELS */ }; #define SPA_AUDIO_INFO_RAW_INIT(...) ((struct spa_audio_info_raw) { __VA_ARGS__ }) diff --git a/spa/lib/lib.c b/spa/lib/lib.c index 165fc81ba..30aa91194 100644 --- a/spa/lib/lib.c +++ b/spa/lib/lib.c @@ -1,4 +1,6 @@ +#undef SPA_AUDIO_MAX_CHANNELS + #define SPA_API_IMPL SPA_EXPORT #include #include @@ -165,9 +167,3 @@ #include #include #include - - - - - - From 78219471fffa5e147502e7cc670867a2404dbe63 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 09:35:59 +0200 Subject: [PATCH 11/31] spa: remove some obsolete functions The spa_audio_info array now always holds enough positions for all channels and we don't need to wrap around. --- spa/include/spa/param/audio/raw-utils.h | 29 ---------------------- spa/plugins/audioconvert/audioconvert.c | 20 +++++++-------- spa/plugins/bluez5/bluez5-dbus.c | 8 ++++-- spa/plugins/bluez5/bluez5-device.c | 5 +++- src/modules/module-combine-stream.c | 4 +-- src/modules/module-jack-tunnel.c | 3 +-- src/modules/module-loopback.c | 2 +- src/modules/module-netjack2-driver.c | 9 +++---- src/modules/module-netjack2-manager.c | 9 +++---- src/modules/module-protocol-pulse/format.c | 2 +- src/modules/module-protocol-pulse/module.c | 5 ++-- src/modules/module-pulse-tunnel.c | 2 +- 12 files changed, 34 insertions(+), 64 deletions(-) diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 551f47af1..ea7097a69 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -28,35 +28,6 @@ extern "C" { #endif #endif -SPA_API_AUDIO_RAW_UTILS uint32_t -spa_format_audio_raw_get_position(const struct spa_audio_info_raw *info, uint32_t idx) -{ - uint32_t pos, max_position = SPA_N_ELEMENTS(info->position); - if (idx < max_position) { - pos = info->position[idx]; - } else { - pos = info->position[idx % max_position]; - if (SPA_AUDIO_CHANNEL_IS_AUX(pos)) - pos += (idx / max_position) * max_position; - } - return pos; -} -SPA_API_AUDIO_RAW_UTILS void -spa_format_audio_raw_set_position(struct spa_audio_info_raw *info, uint32_t idx, uint32_t position) -{ - if (idx < SPA_N_ELEMENTS(info->position)) - info->position[idx] = position; -} - -SPA_API_AUDIO_RAW_UTILS uint32_t -spa_format_audio_raw_copy_positions(const struct spa_audio_info_raw *info, uint32_t *position, uint32_t max_position) -{ - uint32_t i, n_pos = SPA_MIN(info->channels, max_position); - for (i = 0; i < n_pos; i++) - position[i] = spa_format_audio_raw_get_position(info, i); - return n_pos; -} - SPA_API_AUDIO_RAW_UTILS int spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_info_raw *info, size_t size) { diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 1b4ae18fb..e40c4f2ab 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1897,7 +1897,7 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m this->dir[SPA_DIRECTION_OUTPUT].n_ports = dir->n_ports + 1; for (i = 0; i < dir->n_ports; i++) { - uint32_t pos = spa_format_audio_raw_get_position(&info->info.raw, i); + uint32_t pos = info->info.raw.position[i]; init_port(this, direction, i, pos, true, false, false); if (this->monitor && direction == SPA_DIRECTION_INPUT) init_port(this, SPA_DIRECTION_OUTPUT, i+1, @@ -2067,8 +2067,8 @@ static int setup_in_convert(struct impl *this) uint32_t pi, pj; char b1[8], b2[8]; - pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); - pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); + pi = src_info.info.raw.position[i]; + pj = dst_info.info.raw.position[j]; if (pi != pj) continue; in->remap[i] = j; @@ -2078,7 +2078,7 @@ static int setup_in_convert(struct impl *this) i, in->remap[i], j, spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"), spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK")); - spa_format_audio_raw_set_position(&dst_info.info.raw, j, -1); + dst_info.info.raw.position[j] = -1; break; } } @@ -2126,7 +2126,7 @@ static int remap_volumes(struct impl *this, const struct spa_audio_info *info) for (i = 0; i < p->n_channels; i++) { for (j = i; j < target; j++) { - uint32_t pj = spa_format_audio_raw_get_position(&info->info.raw, j); + uint32_t pj = info->info.raw.position[j]; spa_log_debug(this->log, "%d %d: %d <-> %d", i, j, p->channel_map[i], pj); if (p->channel_map[i] != pj) @@ -2142,7 +2142,7 @@ static int remap_volumes(struct impl *this, const struct spa_audio_info *info) } p->n_channels = target; for (i = 0; i < p->n_channels; i++) - p->channel_map[i] = spa_format_audio_raw_get_position(&info->info.raw, i); + p->channel_map[i] = info->info.raw.position[i]; if (target == 0) return 0; @@ -2216,7 +2216,7 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi src_mask |= 1ULL << (p < 64 ? p : 0); } for (i = 0, dst_mask = 0; i < dst_chan; i++) { - p = spa_format_audio_raw_get_position(&out->format.info.raw, i); + p = out->format.info.raw.position[i]; dst_mask |= 1ULL << (p < 64 ? p : 0); } @@ -2361,8 +2361,8 @@ static int setup_out_convert(struct impl *this) uint32_t pi, pj; char b1[8], b2[8]; - pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); - pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); + pi = src_info.info.raw.position[i]; + pj = dst_info.info.raw.position[j]; if (pi != pj) continue; out->remap[i] = j; @@ -2374,7 +2374,7 @@ static int setup_out_convert(struct impl *this) spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"), spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK")); - spa_format_audio_raw_set_position(&dst_info.info.raw, j, -1); + dst_info.info.raw.position[j] = -1; break; } } diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 2e92696bc..ac91c6cff 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -5038,9 +5038,13 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, spa_log_error(monitor->log, "invalid transport configuration"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } + if (info.info.raw.channels > MAX_CHANNELS) { + spa_log_error(monitor->log, "too many channels in transport"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } transport->n_channels = info.info.raw.channels; - spa_format_audio_raw_copy_positions(&info.info.raw, - transport->channels, SPA_N_ELEMENTS(transport->channels)); + memcpy(transport->channels, info.info.raw.position, + transport->n_channels * sizeof(uint32_t)); } else { transport->n_channels = 2; transport->channels[0] = SPA_AUDIO_CHANNEL_FL; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 838bb54f3..796c73c1c 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -475,7 +475,10 @@ static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t return; } - *n_channels = spa_format_audio_raw_copy_positions(&info.info.raw, channels, max_channels); + *n_channels = info.info.raw.channels; + memcpy(channels, info.info.raw.position, + info.info.raw.channels * sizeof(uint32_t)); + } static const char *get_channel_name(uint32_t channel) diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index a9833d68f..29f57cb50 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -883,8 +883,8 @@ static int create_stream(struct stream_info *info) s->remap[i] = i; for (j = 0; j < tmp_info.channels; j++) { uint32_t pj, pi; - pj = spa_format_audio_raw_get_position(&tmp_info, j); - pi = spa_format_audio_raw_get_position(&remap_info, i); + pj = tmp_info.position[j]; + pi = remap_info.position[i]; if (pj == pi) { s->remap[i] = j; break; diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 8e8065c26..637001423 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -525,8 +525,7 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_type_audio_channel_make_short_name( - spa_format_audio_raw_get_position(&s->info, i), - pos, sizeof(pos), NULL); + s->info.position[i], pos, sizeof(pos), NULL); if (str) snprintf(name, sizeof(name), "%s_%s", prefix, str); else diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 976a8c7a4..e921cd701 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -546,7 +546,7 @@ static void param_format_changed(struct impl *impl, const struct spa_pod *param, (impl->info.channels != 0 && (impl->info.channels != info.channels || memcmp(impl->info.position, info.position, - SPA_MIN(info.channels, SPA_N_ELEMENTS(info.position)) * sizeof(uint32_t)) != 0))) { + info.channels * sizeof(uint32_t)) != 0))) { uint8_t buffer[1024]; struct spa_pod_builder b; const struct spa_pod *params[1]; diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index e98ac9584..485570064 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -442,8 +442,7 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_type_audio_channel_make_short_name( - spa_format_audio_raw_get_position(&s->info, i), - name, sizeof(name), "UNK"); + s->info.position[i], name, sizeof(name), "UNK"); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_AUDIO_CHANNEL, str, @@ -866,8 +865,7 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p if ((uint32_t)peer->params.send_audio_channels != impl->sink.info.channels) { impl->sink.info.channels = peer->params.send_audio_channels; for (i = 0; i < impl->sink.info.channels; i++) - spa_format_audio_raw_set_position(&impl->sink.info, i, - SPA_AUDIO_CHANNEL_AUX0 + i); + impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } impl->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; if (impl->source.n_ports > MAX_PORTS) { @@ -878,8 +876,7 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p if ((uint32_t)peer->params.recv_audio_channels != impl->source.info.channels) { impl->source.info.channels = peer->params.recv_audio_channels; for (i = 0; i < impl->source.info.channels; i++) - spa_format_audio_raw_set_position(&impl->source.info, i, - SPA_AUDIO_CHANNEL_AUX0 + i); + impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } impl->samplerate = peer->params.sample_rate; impl->period_size = peer->params.period_size; diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 4f3a1cf73..615a4553b 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -602,8 +602,7 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_type_audio_channel_make_short_name( - spa_format_audio_raw_get_position(&s->info, i), - name, sizeof(name), "UNK"); + s->info.position[i], name, sizeof(name), "UNK"); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_AUDIO_CHANNEL, str, @@ -1031,16 +1030,14 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param if ((uint32_t)peer->params.recv_audio_channels != follower->source.info.channels) { follower->source.info.channels = peer->params.recv_audio_channels; for (i = 0; i < follower->source.info.channels; i++) - spa_format_audio_raw_set_position(&follower->source.info, i, - SPA_AUDIO_CHANNEL_AUX0 + i); + follower->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } follower->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; follower->sink.info.rate = peer->params.sample_rate; if ((uint32_t)peer->params.send_audio_channels != follower->sink.info.channels) { follower->sink.info.channels = peer->params.send_audio_channels; for (i = 0; i < follower->sink.info.channels; i++) - spa_format_audio_raw_set_position(&follower->sink.info, i, - SPA_AUDIO_CHANNEL_AUX0 + i); + follower->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } if (follower->source.n_ports > MAX_PORTS || follower->sink.n_ports > MAX_PORTS) { diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index b34765641..24ef024f9 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -585,7 +585,7 @@ int format_parse_param(const struct spa_pod *param, bool collect, if (info.info.raw.channels) { map->channels = SPA_MIN(info.info.raw.channels, CHANNELS_MAX); for (i = 0; i < map->channels; i++) - map->map[i] = spa_format_audio_raw_get_position(&info.info.raw, i); + map->map[i] = info.info.raw.position[i]; } } return 0; diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c index fd997883e..2b47d93e9 100644 --- a/src/modules/module-protocol-pulse/module.c +++ b/src/modules/module-protocol-pulse/module.c @@ -244,7 +244,7 @@ int module_args_to_audioinfo_keys(struct impl *impl, struct pw_properties *props } else { /* FIXME add more mappings */ for (i = 0; i < info->channels; i++) - spa_format_audio_raw_set_position(info, i, SPA_AUDIO_CHANNEL_UNKNOWN); + info->position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; } if (info->position[0] == SPA_AUDIO_CHANNEL_UNKNOWN) info->flags |= SPA_AUDIO_FLAG_UNPOSITIONED; @@ -290,8 +290,7 @@ void audioinfo_to_properties(struct spa_audio_info_raw *info, struct pw_properti p = s = alloca(info->channels * 8); for (i = 0; i < info->channels; i++) p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ", ", - channel_id2name(spa_format_audio_raw_get_position(info, i), - pos, sizeof(pos))); + channel_id2name(info->position[i], pos, sizeof(pos))); pw_properties_setf(props, SPA_KEY_AUDIO_POSITION, "[ %s ]", s); } } diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index d613fb13b..4c4ab1a26 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -755,7 +755,7 @@ static int create_pulse_stream(struct impl *impl) map.channels = impl->info.channels; for (i = 0; i < map.channels; i++) map.map[i] = (pa_channel_position_t)channel_id2pa( - spa_format_audio_raw_get_position(&impl->info, i), &aux); + impl->info.position[i], &aux); snprintf(stream_name, sizeof(stream_name), _("Tunnel for %s@%s"), pw_get_user_name(), pw_get_host_name()); From aa0272f6f357706307ec4cab7296fd4f691db2de Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 10:28:38 +0200 Subject: [PATCH 12/31] treewide: remove some obsolete channel checks The spa_audio_info can not be parsed with too many channels so there is always enough space for the positions. --- pipewire-alsa/alsa-plugins/pcm_pipewire.c | 31 ++++++++----------- spa/include/spa/param/audio/raw-utils.h | 4 ++- spa/plugins/audioconvert/audioconvert.c | 36 ++++++++++------------- spa/plugins/bluez5/a2dp-codec-opus.c | 15 +++++----- spa/plugins/bluez5/bluez5-device.c | 5 ++-- src/modules/module-ffado-driver.c | 6 ++-- src/modules/module-filter-chain.c | 3 +- src/modules/module-vban/stream.c | 14 ++++----- 8 files changed, 50 insertions(+), 64 deletions(-) diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index bf7196898..24a66eabc 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -643,11 +643,8 @@ static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable) #define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE #endif -static int set_default_channels(uint32_t channels, uint32_t *position, uint32_t max_position) +static int set_default_channels(uint32_t channels, uint32_t position[MAX_CHANNELS]) { - if (max_position < 8) - return -ENOSPC; - switch (channels) { case 8: position[6] = SPA_AUDIO_CHANNEL_SL; @@ -775,8 +772,7 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, case SPA_MEDIA_SUBTYPE_raw: pw->requested.info.raw.channels = io->channels; pw->requested.info.raw.rate = io->rate; - set_default_channels(io->channels, pw->requested.info.raw.position, - SPA_N_ELEMENTS(pw->requested.info.raw.position)); + set_default_channels(io->channels, pw->requested.info.raw.position); fmt_str = spa_type_audio_format_to_short_name(pw->requested.info.raw.format); pw->format = pw->requested; break; @@ -784,8 +780,7 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, pw->requested.info.dsd.bitorder = SPA_PARAM_BITORDER_msb; pw->requested.info.dsd.channels = io->channels; pw->requested.info.dsd.rate = io->rate * SPA_ABS(pw->requested.info.dsd.interleave); - set_default_channels(io->channels, pw->requested.info.dsd.position, - SPA_N_ELEMENTS(pw->requested.info.dsd.position)); + set_default_channels(io->channels, pw->requested.info.dsd.position); pw->format = pw->requested; /* we need to let the server decide these values */ pw->format.info.dsd.bitorder = 0; @@ -907,30 +902,30 @@ static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io, { snd_pcm_pipewire_t *pw = io->private_data; unsigned int i; - uint32_t *position, max_position; + uint32_t *position; switch (pw->requested.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: pw->requested.info.raw.channels = map->channels; position = pw->requested.info.raw.position; - max_position = SPA_N_ELEMENTS(pw->requested.info.raw.position); break; case SPA_MEDIA_SUBTYPE_dsd: pw->requested.info.dsd.channels = map->channels; position = pw->requested.info.dsd.position; - max_position = SPA_N_ELEMENTS(pw->requested.info.dsd.position); break; default: return -EINVAL; } + if (map->channels > MAX_CHANNELS) + return -ENOTSUP; + for (i = 0; i < map->channels; i++) { - uint32_t pos = chmap_to_channel(map->pos[i]); char buf[8]; - if (i < max_position) - position[i] = pos; + position[i] = chmap_to_channel(map->pos[i]); pw_log_debug("map %d: %s / %s", i, snd_pcm_chmap_name(map->pos[i]), - spa_type_audio_channel_make_short_name(pos, buf, sizeof(buf), "UNK")); + spa_type_audio_channel_make_short_name(position[i], + buf, sizeof(buf), "UNK")); } return 1; } @@ -939,18 +934,16 @@ static snd_pcm_chmap_t * snd_pcm_pipewire_get_chmap(snd_pcm_ioplug_t * io) { snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_chmap_t *map; - uint32_t i, channels, *position, max_position; + uint32_t i, channels, *position; switch (pw->requested.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: channels = pw->requested.info.raw.channels; position = pw->requested.info.raw.position; - max_position = SPA_N_ELEMENTS(pw->requested.info.raw.position); break; case SPA_MEDIA_SUBTYPE_dsd: channels = pw->requested.info.dsd.channels; position = pw->requested.info.dsd.position; - max_position = SPA_N_ELEMENTS(pw->requested.info.dsd.position); break; default: return NULL; @@ -960,7 +953,7 @@ static snd_pcm_chmap_t * snd_pcm_pipewire_get_chmap(snd_pcm_ioplug_t * io) channels * sizeof(unsigned int)); map->channels = channels; for (i = 0; i < channels; i++) - map->pos[i] = channel_to_chmap(position[i % max_position]); + map->pos[i] = channel_to_chmap(position[i]); return map; } diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index ea7097a69..ce5e61e67 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -43,7 +43,7 @@ spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_in SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); if (info->channels > max_position) - return -ENOTSUP; + return -ECHRNG; if (position == NULL || spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); @@ -78,6 +78,8 @@ spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id, if (info->channels != 0) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); + /* we drop the positions here when we can't read all of them. This is + * really a malformed spa_audio_info structure. */ if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED) && max_position > info->channels) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position, diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index e40c4f2ab..e8c95b44a 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1148,7 +1148,7 @@ struct spa_filter_graph_events graph_events = { }; static int setup_filter_graph(struct impl *this, struct filter_graph *g, - uint32_t channels, uint32_t *position, uint32_t max_position) + uint32_t channels, uint32_t *position) { int res; char rate_str[64], in_ports[64]; @@ -1163,9 +1163,8 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, snprintf(in_ports, sizeof(in_ports), "%d", channels); g->n_inputs = channels; if (position) { - uint32_t n_pos = SPA_MIN(channels, max_position); - memcpy(g->inputs_position, position, sizeof(uint32_t) * n_pos); - memcpy(g->outputs_position, position, sizeof(uint32_t) * n_pos); + memcpy(g->inputs_position, position, sizeof(uint32_t) * channels); + memcpy(g->outputs_position, position, sizeof(uint32_t) * channels); } } @@ -1180,7 +1179,7 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, return res; } -static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position, uint32_t max_position); +static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position); static void free_tmp(struct impl *this) { @@ -1262,7 +1261,7 @@ static int ensure_tmp(struct impl *this) static int setup_filter_graphs(struct impl *impl, bool force) { int res; - uint32_t channels, *position, max_position; + uint32_t channels, *position; struct dir *in, *out; struct filter_graph *g, *t; @@ -1271,7 +1270,6 @@ static int setup_filter_graphs(struct impl *impl, bool force) channels = in->format.info.raw.channels; position = in->format.info.raw.position; - max_position = SPA_N_ELEMENTS(in->format.info.raw.position); impl->maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); spa_list_for_each_safe(g, t, &impl->active_graphs, link) { @@ -1279,20 +1277,19 @@ static int setup_filter_graphs(struct impl *impl, bool force) continue; if (force) g->setup = false; - if ((res = setup_filter_graph(impl, g, channels, position, max_position)) < 0) { + if ((res = setup_filter_graph(impl, g, channels, position)) < 0) { g->removing = true; spa_log_warn(impl->log, "failed to activate graph %d: %s", g->order, spa_strerror(res)); } else { channels = g->n_outputs; position = g->outputs_position; - max_position = SPA_N_ELEMENTS(g->outputs_position); impl->maxports = SPA_MAX(impl->maxports, channels); } } if ((res = ensure_tmp(impl)) < 0) return res; - if ((res = setup_channelmix(impl, channels, position, max_position)) < 0) + if ((res = setup_channelmix(impl, channels, position)) < 0) return res; return 0; @@ -2058,8 +2055,7 @@ static int setup_in_convert(struct impl *this) dst_info.info.raw.channels, dst_info.info.raw.rate); - qsort(dst_info.info.raw.position, SPA_MIN(dst_info.info.raw.channels, - SPA_N_ELEMENTS(dst_info.info.raw.position)), + qsort(dst_info.info.raw.position, dst_info.info.raw.channels, sizeof(uint32_t), int32_cmp); for (i = 0; i < src_info.info.raw.channels; i++) { @@ -2188,18 +2184,18 @@ static void set_volume(struct impl *this) this->params[IDX_Props].user++; } -static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position, uint32_t max_position) +static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position) { uint32_t i, idx = 0; char buf[8]; for (i = 0; i < channels; i++) idx += snprintf(str + idx, len - idx, "%s%s", i == 0 ? "" : " ", - spa_type_audio_channel_make_short_name(position[i % max_position], + spa_type_audio_channel_make_short_name(position[i], buf, sizeof(buf), "UNK")); return str; } -static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position, uint32_t max_position) +static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position) { struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; @@ -2212,7 +2208,7 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi dst_chan = out->format.info.raw.channels; for (i = 0, src_mask = 0; i < src_chan; i++) { - p = position[i % max_position]; + p = position[i]; src_mask |= 1ULL << (p < 64 ? p : 0); } for (i = 0, dst_mask = 0; i < dst_chan; i++) { @@ -2221,10 +2217,9 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi } spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), - src_chan, position, max_position), src_mask); + src_chan, position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), - dst_chan, out->format.info.raw.position, - SPA_N_ELEMENTS(out->format.info.raw.position)), dst_mask); + dst_chan, out->format.info.raw.position), dst_mask); spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d %08"PRIx64":%08"PRIx64, this, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), @@ -2352,8 +2347,7 @@ static int setup_out_convert(struct impl *this) dst_info.info.raw.channels, dst_info.info.raw.rate); - qsort(src_info.info.raw.position, SPA_MIN(src_info.info.raw.channels, - SPA_N_ELEMENTS(src_info.info.raw.position)), + qsort(src_info.info.raw.position, src_info.info.raw.channels, sizeof(uint32_t), int32_cmp); for (i = 0; i < src_info.info.raw.channels; i++) { diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c index 0881c0255..c5b042b20 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus.c +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -491,7 +491,7 @@ static void get_default_bitrates(const struct media_codec *codec, bool bidi, int static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direction_t *conf, bool use_surround_encoder, uint8_t *streams_ret, uint8_t *coupled_streams_ret, - const uint8_t **surround_mapping, uint32_t *positions, uint32_t max_positions) + const uint8_t **surround_mapping, uint32_t *positions) { const uint32_t channels = conf->channels; const uint32_t location = OPUS_05_GET_LOCATION(*conf); @@ -539,12 +539,11 @@ static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direc if (location & loc.mask) { uint32_t idx = permutation ? permutation[j] : j; - if (idx < max_positions) - positions[idx] = loc.position; + positions[idx] = loc.position; j++; } } - for (i = SPA_AUDIO_CHANNEL_START_Aux; j < channels && j < max_positions; ++i, ++j) + for (i = SPA_AUDIO_CHANNEL_START_Aux; j < channels; ++i, ++j) positions[j] = i; } @@ -779,7 +778,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, dir = !is_duplex_codec(codec) ? &conf.main : &conf.bidi; - if (get_mapping(codec, dir, surround_encoder, NULL, NULL, NULL, position, MAX_CHANNELS) < 0) + if (get_mapping(codec, dir, surround_encoder, NULL, NULL, NULL, position) < 0) return -EINVAL; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); @@ -832,9 +831,9 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags info->info.raw.channels = dir1->channels; if (get_mapping(codec, dir1, surround_encoder, NULL, NULL, NULL, - info->info.raw.position, SPA_N_ELEMENTS(info->info.raw.position)) < 0) + info->info.raw.position) < 0) return -EINVAL; - if (get_mapping(codec, dir2, surround_encoder, NULL, NULL, NULL, NULL, 0) < 0) + if (get_mapping(codec, dir2, surround_encoder, NULL, NULL, NULL, NULL) < 0) return -EINVAL; return 0; @@ -925,7 +924,7 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0) goto error; if ((res = get_mapping(codec, dir, surround_encoder, &this->streams, &this->coupled_streams, - &enc_mapping, NULL, 0)) < 0) + &enc_mapping, NULL)) < 0) goto error; if (config_info.info.raw.channels != info->info.raw.channels) { res = -EINVAL; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 796c73c1c..6546abf4f 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -452,8 +452,7 @@ static int node_offload_set_active(struct node *node, bool active) return res; } -static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels, - uint32_t max_channels) +static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels) { const struct media_codec *codec; struct spa_audio_info info = { 0 }; @@ -689,7 +688,7 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, this->nodes[id].active = true; this->nodes[id].offload_acquired = false; this->nodes[id].a2dp_duplex = a2dp_duplex; - get_channels(t, a2dp_duplex, &this->nodes[id].n_channels, this->nodes[id].channels, MAX_CHANNELS); + get_channels(t, a2dp_duplex, &this->nodes[id].n_channels, this->nodes[id].channels); if (this->nodes[id].transport) spa_hook_remove(&this->nodes[id].transport_listener); this->nodes[id].transport = t; diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index de5ed0ff0..7094feaf4 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -761,7 +761,7 @@ static int make_stream_ports(struct stream *s) struct port *port = s->ports[i]; char channel[32]; - snprintf(channel, sizeof(channel), "AUX%u", n_channels % MAX_CHANNELS); + snprintf(channel, sizeof(channel), "AUX%u", n_channels); switch (port->stream_type) { case ffado_stream_type_audio: @@ -1229,7 +1229,7 @@ static int probe_ffado_device(struct impl *impl) } if (impl->source.info.channels != n_channels) { uint32_t n_pos = SPA_MIN(n_channels, SPA_N_ELEMENTS(impl->source.info.position)); - impl->source.info.channels = n_channels; + impl->source.info.channels = n_pos; for (i = 0; i < n_pos; i++) impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } @@ -1256,7 +1256,7 @@ static int probe_ffado_device(struct impl *impl) } if (impl->sink.info.channels != n_channels) { uint32_t n_pos = SPA_MIN(n_channels, SPA_N_ELEMENTS(impl->sink.info.position)); - impl->sink.info.channels = n_channels; + impl->sink.info.channels = n_pos; for (i = 0; i < n_pos; i++) impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 6b759434b..a2d1f5361 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1691,8 +1691,7 @@ static void copy_position(struct spa_audio_info_raw *dst, const struct spa_audio { if (SPA_FLAG_IS_SET(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED) && !SPA_FLAG_IS_SET(src->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { - uint32_t i, n_pos = SPA_MIN(src->channels, SPA_N_ELEMENTS(dst->position)); - for (i = 0; i < n_pos; i++) + for (uint32_t i = 0; i < src->channels; i++) dst->position[i] = src->position[i]; SPA_FLAG_CLEAR(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED); } diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index 22ef8bea5..941fa1acd 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -215,16 +215,16 @@ static const struct spa_audio_layout_info layouts[] = { { SPA_AUDIO_LAYOUT_7_1 }, }; -static void default_layout(uint32_t channels, uint32_t *position, uint32_t max_position) +static void default_layout(uint32_t channels, uint32_t *position) { SPA_FOR_EACH_ELEMENT_VAR(layouts, l) { if (l->n_channels == channels) { - for (uint32_t i = 0; i < l->n_channels && i < max_position; i++) + for (uint32_t i = 0; i < l->n_channels; i++) position[i] = l->position[i]; return; } } - for (uint32_t i = 0; i < channels && i < max_position; i++) + for (uint32_t i = 0; i < channels; i++) position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } @@ -278,10 +278,10 @@ struct vban_stream *vban_stream_new(struct pw_core *core, pw_log_error("can't parse format: %s", spa_strerror(res)); goto out; } - if (SPA_FLAG_IS_SET(impl->info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED)) - default_layout(impl->info.info.raw.channels, - impl->info.info.raw.position, - SPA_N_ELEMENTS(impl->info.info.raw.position)); + if (SPA_FLAG_IS_SET(impl->info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { + default_layout(impl->info.info.raw.channels, impl->info.info.raw.position); + SPA_FLAG_CLEAR(impl->info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED); + } impl->stream_info = impl->info; impl->format_info = find_audio_format_info(&impl->info); if (impl->format_info == NULL) { From d18670d7bbfe88aaee7bc3c8926eb1c4fedd5173 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 10:42:05 +0200 Subject: [PATCH 13/31] pw-cat: improve channel checks Make sure we don't use too many channels. --- src/tools/pw-cat.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 8210addb6..632a513bf 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -2348,10 +2348,22 @@ int main(int argc, char *argv[]) .rate = data.rate, .channels = data.channels); + if (data.channels > MAX_CHANNELS) { + fprintf(stderr, "error: too many channels %d > %d\n", + data.channels, MAX_CHANNELS); + goto error_bad_file; + } if (data.channelmap.n_channels) { - uint32_t i, n_pos = SPA_MIN(data.channels, SPA_N_ELEMENTS(info.position)); - for (i = 0; i < n_pos; i++) + if (data.channels > MAX_CHANNELS) { + fprintf(stderr, "error: too many channels in channelmap %d > %d\n", + data.channelmap.n_channels, MAX_CHANNELS); + goto error_bad_file; + } + uint32_t i; + for (i = 0; i < data.channelmap.n_channels; i++) info.position[i] = data.channelmap.channels[i]; + for (; i < data.channels; i++) + info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info); break; From f7c3d379698e0c8b0eefec6cbab10a0b04f0bf8a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 12:46:38 +0200 Subject: [PATCH 14/31] fmt-ops: allocate shaper memory dynamically It is based on the number of channels so allocate it dynamically. --- spa/plugins/audioconvert/fmt-ops.c | 6 ++++-- spa/plugins/audioconvert/fmt-ops.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/spa/plugins/audioconvert/fmt-ops.c b/spa/plugins/audioconvert/fmt-ops.c index 3296c220b..3fc2c5f0a 100644 --- a/spa/plugins/audioconvert/fmt-ops.c +++ b/spa/plugins/audioconvert/fmt-ops.c @@ -551,7 +551,7 @@ int convert_init(struct convert *conv) const struct dither_info *dinfo; const struct noise_info *ninfo; const struct clear_info *cinfo; - uint32_t i, conv_flags, data_size[3]; + uint32_t i, conv_flags, data_size[4]; /* we generate int32 bits of random values. With this scale * factor, we bring this in the [-1.0, 1.0] range */ @@ -615,15 +615,17 @@ int convert_init(struct convert *conv) data_size[0] = SPA_ROUND_UP(conv->noise_size * sizeof(float), FMT_OPS_MAX_ALIGN); data_size[1] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(uint32_t), FMT_OPS_MAX_ALIGN); data_size[2] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(int32_t), FMT_OPS_MAX_ALIGN); + data_size[3] = SPA_ROUND_UP(conv->n_channels * sizeof(struct shaper), FMT_OPS_MAX_ALIGN); conv->data = calloc(FMT_OPS_MAX_ALIGN + - data_size[0] + data_size[1] + data_size[2], 1); + data_size[0] + data_size[1] + data_size[2] + data_size[3], 1); if (conv->data == NULL) return -errno; conv->noise = SPA_PTR_ALIGN(conv->data, FMT_OPS_MAX_ALIGN, float); conv->random = SPA_PTROFF(conv->noise, data_size[0], uint32_t); conv->prev = SPA_PTROFF(conv->random, data_size[1], int32_t); + conv->shaper = SPA_PTROFF(conv->prev, data_size[2], struct shaper); for (i = 0; i < RANDOM_SIZE; i++) conv->random[i] = random(); diff --git a/spa/plugins/audioconvert/fmt-ops.h b/spa/plugins/audioconvert/fmt-ops.h index 9f4655c22..f738e3858 100644 --- a/spa/plugins/audioconvert/fmt-ops.h +++ b/spa/plugins/audioconvert/fmt-ops.h @@ -236,7 +236,7 @@ struct convert { uint32_t noise_size; const float *ns; uint32_t n_ns; - struct shaper shaper[64]; + struct shaper *shaper; void (*update_noise) (struct convert *conv, float *noise, uint32_t n_samples); void (*process) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], From c4244a6cf3e042af5c2c344ba0a52836ec45aac8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 17:00:11 +0200 Subject: [PATCH 15/31] string: use spa_strbuf instead of snprintf magic --- spa/plugins/alsa/acp/acp.c | 19 ++++++++------- spa/plugins/alsa/acp/channelmap.h | 5 +--- spa/plugins/alsa/alsa-acp-device.c | 9 +++++--- spa/plugins/alsa/alsa-pcm.c | 37 ++++++++++++++---------------- src/pipewire/settings.c | 19 +++++++-------- 5 files changed, 41 insertions(+), 48 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 364d71eb0..f6f03a5ff 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -245,7 +245,7 @@ static void init_device(pa_card *impl, pa_alsa_device *dev, pa_alsa_direction_t pa_proplist_update(dev->proplist, PA_UPDATE_REPLACE, m->input_proplist); } if (m->split) { - char pos[2048]; + char pos[PA_CHANNELS_MAX*8]; struct spa_strbuf b; int i; @@ -1142,8 +1142,9 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED); } else { uint32_t positions[eld.lpcm_channels]; - char position[64]; - int i = 0, pos = 0; + char position[eld.lpcm_channels * 8]; + struct spa_strbuf b; + int i = 0; if (eld.speakers & 0x01) { positions[i++] = ACP_CHANNEL_FL; @@ -1172,16 +1173,14 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) if (eld.speakers & 0x10) { positions[i++] = ACP_CHANNEL_RC; } - while (i < eld.lpcm_channels) positions[i++] = ACP_CHANNEL_UNKNOWN; - for (i = 0, pos = 0; i < eld.lpcm_channels; i++) { - pos += snprintf(&position[pos], sizeof(position) - pos, "%s,", channel_names[positions[i]]); - } - - /* Overwrite trailing , */ - position[pos - 1] = 0; + spa_strbuf_init(&b, position, sizeof(position)); + spa_strbuf_append(&b, "["); + for (i = 0; i < eld.lpcm_channels; i++) + spa_strbuf_append(&b, "%s%s", i ? "," : "", channel_names[positions[i]]); + spa_strbuf_append(&b, "]"); changed |= (old_position == NULL) || (!spa_streq(old_position, position)); pa_proplist_sets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED, position); diff --git a/spa/plugins/alsa/acp/channelmap.h b/spa/plugins/alsa/acp/channelmap.h index 2d4b54444..bb8f3f5f3 100644 --- a/spa/plugins/alsa/acp/channelmap.h +++ b/spa/plugins/alsa/acp/channelmap.h @@ -451,7 +451,6 @@ static inline int pa_channel_map_equal(const pa_channel_map *a, const pa_channel static inline char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) { unsigned channel; - bool first = true; char *e; if (!pa_channel_map_valid(map)) { pa_snprintf(s, l, "%s", _("(invalid)")); @@ -460,12 +459,10 @@ static inline char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_m *(e = s) = 0; for (channel = 0; channel < map->channels && l > 1; channel++) { l -= pa_snprintf(e, l, "%s%s", - first ? "" : ",", + channel == 0 ? "" : ",", pa_channel_position_to_string(map->map[channel])); e = strchr(e, 0); - first = false; } - return s; } diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index 6ab41c8d2..44342a7a3 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -156,12 +156,13 @@ static int emit_node(struct impl *this, struct acp_device *dev) const struct acp_dict_item *it; uint32_t n_items, i; char device_name[128], path[210], channels[16], ch[12], routes[16]; - char card_index[16], card_name[64], *p; + char card_index[16], card_name[64]; char positions[MAX_CHANNELS * 12]; char codecs[512]; struct spa_device_object_info info; struct acp_card *card = this->card; const char *stream, *card_id, *bus; + struct spa_strbuf b; info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; @@ -200,11 +201,13 @@ static int emit_node(struct impl *this, struct acp_device *dev) snprintf(channels, sizeof(channels), "%d", dev->format.channels); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNELS, channels); - p = positions; + spa_strbuf_init(&b, positions, sizeof(positions)); + spa_strbuf_append(&b, "["); for (i = 0; i < dev->format.channels; i++) { - p += snprintf(p, 12, "%s%s", i == 0 ? "" : ",", + spa_strbuf_append(&b, "%s%s", i == 0 ? " " : ", ", acp_channel_str(ch, sizeof(ch), dev->format.map[i])); } + spa_strbuf_append(&b, " ]"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_POSITION, positions); if (dev->n_codecs > 0) { diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 676214685..309b2d91d 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -240,36 +240,33 @@ static int alsa_set_param(struct state *state, const char *k, const char *s) static int position_to_string(struct channel_map *map, char *val, size_t len) { - uint32_t i, o = 0; + uint32_t i; char pos[8]; - int r; - o += snprintf(val, len, "[ "); + struct spa_strbuf b; + + spa_strbuf_init(&b, val, len); + spa_strbuf_append(&b, "["); for (i = 0; i < map->n_pos; i++) { - r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", + spa_strbuf_append(&b, "%s%s", i == 0 ? " " : ", ", spa_type_audio_channel_make_short_name(map->pos[i], pos, sizeof(pos), "UNK")); - if (r < 0 || o + r >= len) - return -ENOSPC; - o += r; } - if (len > o) - o += snprintf(val+o, len-o, " ]"); + if (spa_strbuf_append(&b, " ]") < 2) + return -ENOSPC; return 0; } static int uint32_array_to_string(uint32_t *vals, uint32_t n_vals, char *val, size_t len) { - uint32_t i, o = 0; - int r; - o += snprintf(val, len, "[ "); - for (i = 0; i < n_vals; i++) { - r = snprintf(val+o, len-o, "%s%d", i == 0 ? "" : ", ", vals[i]); - if (r < 0 || o + r >= len) - return -ENOSPC; - o += r; - } - if (len > o) - o += snprintf(val+o, len-o, " ]"); + uint32_t i; + struct spa_strbuf b; + + spa_strbuf_init(&b, val, len); + spa_strbuf_append(&b, "["); + for (i = 0; i < n_vals; i++) + spa_strbuf_append(&b, "%s%d", i == 0 ? " " : ", ", vals[i]); + if (spa_strbuf_append(&b, " ]") < 2) + return -ENOSPC; return 0; } diff --git a/src/pipewire/settings.c b/src/pipewire/settings.c index 597e686a9..74d4cfcb2 100644 --- a/src/pipewire/settings.c +++ b/src/pipewire/settings.c @@ -245,24 +245,21 @@ void pw_settings_init(struct pw_context *this) static void expose_settings(struct pw_context *context, struct pw_impl_metadata *metadata) { struct settings *s = &context->settings; - uint32_t i, o; - char rates[MAX_RATES*16] = ""; + uint32_t i; + char rates[MAX_RATES*16]; + struct spa_strbuf b; pw_impl_metadata_set_propertyf(metadata, PW_ID_CORE, "log.level", "", "%d", s->log_level); pw_impl_metadata_set_propertyf(metadata, PW_ID_CORE, "clock.rate", "", "%d", s->clock_rate); - for (i = 0, o = 0; i < s->n_clock_rates; i++) { - int r = snprintf(rates+o, sizeof(rates)-o, "%s%d", i == 0 ? "" : ", ", + + spa_strbuf_init(&b, rates, sizeof(rates)); + for (i = 0; i < s->n_clock_rates; i++) + spa_strbuf_append(&b, "%s%d", i == 0 ? "" : ", ", s->clock_rates[i]); - if (r < 0 || o + r >= (int)sizeof(rates)) { - snprintf(rates, sizeof(rates), "%d", s->clock_rate); - break; - } - o += r; - } if (s->n_clock_rates == 0) - snprintf(rates, sizeof(rates), "%d", s->clock_rate); + spa_strbuf_append(&b, "%d", s->clock_rate); pw_impl_metadata_set_propertyf(metadata, PW_ID_CORE, "clock.allowed-rates", "", "[ %s ]", rates); From c8d4de5e77a016d94824e4240c851b5d032c9946 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 17:00:42 +0200 Subject: [PATCH 16/31] acp: bump max channels to 128 --- spa/plugins/alsa/acp/channelmap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/alsa/acp/channelmap.h b/spa/plugins/alsa/acp/channelmap.h index bb8f3f5f3..72e59fdc6 100644 --- a/spa/plugins/alsa/acp/channelmap.h +++ b/spa/plugins/alsa/acp/channelmap.h @@ -27,7 +27,7 @@ extern "C" { #endif -#define PA_CHANNELS_MAX 64 +#define PA_CHANNELS_MAX 128 #define PA_CHANNEL_MAP_SNPRINT_MAX (PA_CHANNELS_MAX * 32) From 88c65932d8d8a002ae9e2d42ca2a25b06a804a19 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 17:16:03 +0200 Subject: [PATCH 17/31] acp: use global max channels if defined --- spa/plugins/alsa/acp/channelmap.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spa/plugins/alsa/acp/channelmap.h b/spa/plugins/alsa/acp/channelmap.h index 72e59fdc6..2b13d2528 100644 --- a/spa/plugins/alsa/acp/channelmap.h +++ b/spa/plugins/alsa/acp/channelmap.h @@ -27,7 +27,11 @@ extern "C" { #endif -#define PA_CHANNELS_MAX 128 +#ifdef SPA_AUDIO_MAX_CHANNELS +#define PA_CHANNELS_MAX ((int)SPA_AUDIO_MAX_CHANNELS) +#else +#define PA_CHANNELS_MAX 64 +#endif #define PA_CHANNEL_MAP_SNPRINT_MAX (PA_CHANNELS_MAX * 32) From 9f1a149876c56ec07ae74b10e9c4ef8a97084bf7 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 22 Oct 2025 19:32:22 +0300 Subject: [PATCH 18/31] ci: add file package, for coverity Try to fix coverity by adding missing 'file' package to container --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9cfde68d0..448bfa06e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -38,7 +38,7 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2025-10-15.0' + FDO_DISTRIBUTION_TAG: '2025-10-22.0' FDO_DISTRIBUTION_VERSION: '42' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel @@ -48,6 +48,7 @@ include: dbus-devel doxygen fdk-aac-free-devel + file findutils gcc gcc-c++ From 93495d3a754bc7492cfb27fd551180f0be9b6932 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 26 Oct 2025 15:38:17 +0200 Subject: [PATCH 19/31] spa: param: infer raw audio channels from position if unset The behavior before b8eeb2db45adfb was that spa_audio_info_raw_update() always sets audio.channels when audio.position is updated. The new behavior does not set audio.channels when parsing audio.position. This breaks things e.g. when combine-sink and loopback nodes are created with only audio.position specified. Restore the previous behavior. --- spa/include/spa/param/audio/raw-json.h | 1 + 1 file changed, 1 insertion(+) diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index c33a9310f..e83c4495b 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -78,6 +78,7 @@ spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, max_position, &v) > 0) { if (v > max_position) return -ECHRNG; + info->channels = v; SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); } } From 3febf09b85c23a6cf69fbba1d250b85820abd76a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Oct 2025 13:22:32 +0300 Subject: [PATCH 20/31] alsa: fix typoed braces in condition + assign --- spa/plugins/alsa/alsa-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 309b2d91d..4f2fde8b6 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -773,7 +773,7 @@ static void bind_ctl_event(struct spa_source *source) snd_ctl_elem_id_alloca(&bound_id); snd_ctl_elem_value_alloca(&old_value); - while ((err = snd_ctl_read(state->ctl, ev) > 0)) { + while ((err = snd_ctl_read(state->ctl, ev)) > 0) { bool changed = false; if (snd_ctl_event_get_type(ev) != SND_CTL_EVENT_ELEM) From fe2c62b9b163ff824647cf7c0ec9d8369f836a0e Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Oct 2025 13:59:47 +0300 Subject: [PATCH 21/31] meson.build: set project cc flags also for native builds Use the build flags also for all native build targets. Avoids spurious warnings in spa-json-dump --- meson.build | 4 ++-- spa/plugins/audioconvert/meson.build | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index e81ce8214..e82ba03b3 100644 --- a/meson.build +++ b/meson.build @@ -118,8 +118,8 @@ cc_flags = common_flags + [ '-DSPA_AUDIO_MAX_CHANNELS=128u', ] add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c') - -cc_flags_native = cc_native.get_supported_arguments(cc_flags) +add_project_arguments(cc_native.get_supported_arguments(cc_flags), + language: 'c', native: true) have_cpp = add_languages('cpp', native: false, required : false) diff --git a/spa/plugins/audioconvert/meson.build b/spa/plugins/audioconvert/meson.build index 394bc11eb..64379c845 100644 --- a/spa/plugins/audioconvert/meson.build +++ b/spa/plugins/audioconvert/meson.build @@ -125,7 +125,7 @@ sparesampledumpcoeffs_sources = [ sparesampledumpcoeffs = executable( 'spa-resample-dump-coeffs', sparesampledumpcoeffs_sources, - c_args : [ cc_flags_native, '-DRESAMPLE_DISABLE_PRECOMP' ], + c_args : [ '-DRESAMPLE_DISABLE_PRECOMP' ], dependencies : [ spa_dep, mathlib_native ], install : false, native : true, From b0e308e0dce9c78a3ee71eab0cd9a2a8f959d47b Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Oct 2025 14:27:10 +0300 Subject: [PATCH 22/31] spa: examples: fix getopt usage + typos in adapter-control --- spa/examples/adapter-control.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spa/examples/adapter-control.c b/spa/examples/adapter-control.c index 6aa36dfaa..0a48f2fd4 100644 --- a/spa/examples/adapter-control.c +++ b/spa/examples/adapter-control.c @@ -578,7 +578,7 @@ static int make_nodes(struct data *data) SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); - if ((res = spa_node_set_param(data->source_node, SPA_PARAM_PortConfig, 0, param) < 0)) { + if ((res = spa_node_set_param(data->source_node, SPA_PARAM_PortConfig, 0, param)) < 0) { printf("can't setup source node %d\n", res); return res; } @@ -647,7 +647,7 @@ static int make_nodes(struct data *data) SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); - if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_PortConfig, 0, param) < 0)) { + if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_PortConfig, 0, param)) < 0) { printf("can't setup sink node %d\n", res); return res; } @@ -987,7 +987,7 @@ int main(int argc, char *argv[]) setlocale(LC_ALL, ""); - while ((c = getopt_long(argc, argv, "hdmstiac:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hd:m:s:t:i:a:c:", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(&data, argv[0], false); From 68dc45cc62a6c79caca5e711faac2727e3b49154 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Oct 2025 13:49:46 +0300 Subject: [PATCH 23/31] audioconvert: simplify volume ramp generation Don't use floating point accumulators, interpolate from sample position. --- spa/plugins/audioconvert/audioconvert.c | 82 ++++++++----------------- 1 file changed, 27 insertions(+), 55 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index e8c95b44a..f201522f8 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1538,8 +1538,6 @@ static int get_ramp_samples(struct impl *this, struct volume_ramp_params *vrp) samples = (vrp->volume_ramp_time * vrp->rate) / 1000; spa_log_info(this->log, "volume ramp samples calculated from time is %d", samples); } - if (!samples) - samples = -1; return samples; } @@ -1550,12 +1548,10 @@ static int get_ramp_step_samples(struct impl *this, struct volume_ramp_params *v if (vrp->volume_ramp_step_samples) samples = vrp->volume_ramp_step_samples; else if (vrp->volume_ramp_step_time) { - /* convert the step time which is in nano seconds to seconds */ - samples = (vrp->volume_ramp_step_time/1000) * (vrp->rate/1000); + /* convert the step time which is in nano seconds to seconds, round up */ + samples = SPA_MAX(1u, vrp->volume_ramp_step_time/1000) * (vrp->rate/1000); spa_log_debug(this->log, "volume ramp step samples calculated from time is %d", samples); } - if (!samples) - samples = -1; return samples; } @@ -1568,76 +1564,52 @@ static float get_volume_at_scale(struct volume_ramp_params *vrp, float value) return 0.0; } -static struct spa_pod *generate_ramp_up_seq(struct impl *this, struct volume_ramp_params *vrp, +static struct spa_pod *generate_ramp_seq(struct impl *this, struct volume_ramp_params *vrp, void *buffer, size_t size) { struct spa_pod_dynamic_builder b; struct spa_pod_frame f[1]; - float start = vrp->start, end = vrp->end, volume_accum = start; - int ramp_samples = get_ramp_samples(this, vrp); - int ramp_step_samples = get_ramp_step_samples(this, vrp); - float volume_step = ((end - start) / (ramp_samples / ramp_step_samples)); - uint32_t volume_offs = 0; + float start = vrp->start, end = vrp->end; + int samples = get_ramp_samples(this, vrp); + int step = get_ramp_step_samples(this, vrp); + int offs = 0; + + if (samples < 0 || step < 0 || (samples > 0 && step == 0)) + return NULL; spa_pod_dynamic_builder_init(&b, buffer, size, 4096); spa_pod_builder_push_sequence(&b.b, &f[0], 0); - spa_log_info(this->log, "generating ramp up sequence from %f to %f with a" - " step value %f at scale %d", start, end, volume_step, vrp->scale); - do { - float vas = get_volume_at_scale(vrp, volume_accum); - spa_log_trace(this->log, "volume accum %f", vas); - spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties); - spa_pod_builder_add_object(&b.b, - SPA_TYPE_OBJECT_Props, 0, - SPA_PROP_volume, SPA_POD_Float(vas)); - volume_accum += volume_step; - volume_offs += ramp_step_samples; - } while (volume_accum < end); - return spa_pod_builder_pop(&b.b, &f[0]); -} + spa_log_info(this->log, "generating ramp sequence from %f to %f with " + "step %d/%d at scale %d", start, end, step, samples, vrp->scale); -static struct spa_pod *generate_ramp_down_seq(struct impl *this, struct volume_ramp_params *vrp, - void *buffer, size_t size) -{ - struct spa_pod_dynamic_builder b; - struct spa_pod_frame f[1]; - int ramp_samples = get_ramp_samples(this, vrp); - int ramp_step_samples = get_ramp_step_samples(this, vrp); - float start = vrp->start, end = vrp->end, volume_accum = start; - float volume_step = ((start - end) / (ramp_samples / ramp_step_samples)); - uint32_t volume_offs = 0; + while (1) { + float pos = (samples == 0) ? end : + SPA_CLAMP(start + (end - start) * offs / samples, + SPA_MIN(start, end), SPA_MAX(start, end)); + float vas = get_volume_at_scale(vrp, pos); - spa_pod_dynamic_builder_init(&b, buffer, size, 4096); - - spa_pod_builder_push_sequence(&b.b, &f[0], 0); - spa_log_info(this->log, "generating ramp down sequence from %f to %f with a" - " step value %f at scale %d", start, end, volume_step, vrp->scale); - do { - float vas = get_volume_at_scale(vrp, volume_accum); - spa_log_trace(this->log, "volume accum %f", vas); - spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties); + spa_log_trace(this->log, "volume %d accum %f", offs, vas); + spa_pod_builder_control(&b.b, offs, SPA_CONTROL_Properties); spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_volume, SPA_POD_Float(vas)); - volume_accum -= volume_step; - volume_offs += ramp_step_samples; - } while (volume_accum > end); + if (offs >= samples) + break; + + offs = SPA_MIN(samples, offs + step); + } + return spa_pod_builder_pop(&b.b, &f[0]); } static void generate_volume_ramp(struct impl *this, struct volume_ramp_params *vrp, void *buffer, size_t size) { - void *sequence = NULL; - if (vrp->start == vrp->end) - spa_log_error(this->log, "no change in volume, cannot ramp volume"); - else if (vrp->end > vrp->start) - sequence = generate_ramp_up_seq(this, vrp, buffer, size); - else - sequence = generate_ramp_down_seq(this, vrp, buffer, size); + void *sequence; + sequence = generate_ramp_seq(this, vrp, buffer, size); if (!sequence) spa_log_error(this->log, "unable to generate sequence"); From 3d08c0557f99d21c7fba9dd532ee14d683e440e2 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Oct 2025 15:14:42 +0300 Subject: [PATCH 24/31] properties: fix assign + conditional expression --- src/pipewire/properties.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pipewire/properties.c b/src/pipewire/properties.c index de81088b2..ac0aac0d0 100644 --- a/src/pipewire/properties.c +++ b/src/pipewire/properties.c @@ -252,7 +252,8 @@ static int update_string(struct pw_properties *props, const char *str, size_t si continue; } /* item changed or added, apply changes later */ - if ((errno = -add_item(&changes, key, false, val, true) < 0)) { + if ((res = add_item(&changes, key, false, val, true)) < 0) { + errno = -res; it[0].state = SPA_JSON_ERROR_FLAG; break; } From 8a23b13798e52ea39dc2eb92dd134bae95d6c6a4 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 26 Oct 2025 17:44:03 +0200 Subject: [PATCH 25/31] spa: param: pass correct struct size to spa_format_audio_raw_ext_parse/build --- spa/include/spa/param/audio/format-utils.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spa/include/spa/param/audio/format-utils.h b/spa/include/spa/param/audio/format-utils.h index 07f5d94e3..86ea84f75 100644 --- a/spa/include/spa/param/audio/format-utils.h +++ b/spa/include/spa/param/audio/format-utils.h @@ -59,7 +59,8 @@ spa_format_audio_ext_parse(const struct spa_pod *format, struct spa_audio_info * switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - return spa_format_audio_raw_ext_parse(format, &info->info.raw, size); + return spa_format_audio_raw_ext_parse(format, &info->info.raw, + size - offsetof(struct spa_audio_info, info.raw)); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_audio_dsp_parse(format, &info->info.dsp); case SPA_MEDIA_SUBTYPE_iec958: @@ -110,7 +111,8 @@ spa_format_audio_ext_build(struct spa_pod_builder *builder, uint32_t id, { switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - return spa_format_audio_raw_ext_build(builder, id, &info->info.raw, size); + return spa_format_audio_raw_ext_build(builder, id, &info->info.raw, + size - offsetof(struct spa_audio_info, info.raw)); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_audio_dsp_build(builder, id, &info->info.dsp); case SPA_MEDIA_SUBTYPE_iec958: From c6d0b364ab0657a66687a761b602100f823b8957 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 26 Oct 2025 17:51:01 +0200 Subject: [PATCH 26/31] spa: param: add size checks for spa_audio_info* structs In the API that take struct size for spa_audio_info*, also check the struct size. --- spa/include/spa/param/audio/format-utils.h | 51 +++++++- spa/include/spa/param/audio/format.h | 2 + spa/include/spa/param/audio/raw-json.h | 7 ++ spa/include/spa/param/audio/raw-utils.h | 8 ++ spa/include/spa/param/audio/raw.h | 2 + test/meson.build | 1 + test/test-spa-format.c | 129 +++++++++++++++++++++ 7 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 test/test-spa-format.c diff --git a/spa/include/spa/param/audio/format-utils.h b/spa/include/spa/param/audio/format-utils.h index 86ea84f75..24d06dd1e 100644 --- a/spa/include/spa/param/audio/format-utils.h +++ b/spa/include/spa/param/audio/format-utils.h @@ -46,18 +46,58 @@ extern "C" { #endif #endif +SPA_API_AUDIO_FORMAT_UTILS bool +spa_format_audio_ext_valid_size(uint32_t media_subtype, size_t size) +{ + switch (media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + return size >= offsetof(struct spa_audio_info, info.raw) && + SPA_AUDIO_INFO_RAW_VALID_SIZE(size - offsetof(struct spa_audio_info, info.raw)); + +#define _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(format) \ + case SPA_MEDIA_SUBTYPE_ ## format: \ + return size >= offsetof(struct spa_audio_info, info.format) + sizeof(struct spa_audio_info_ ## format); + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dsp) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(iec958) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dsd) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(mp3) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(aac) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(vorbis) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(wma) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ra) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(amr) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(alac) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(flac) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ape) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ac3) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(eac3) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(truehd) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dts) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(mpegh) +#undef _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE + } + return false; +} + SPA_API_AUDIO_FORMAT_UTILS int spa_format_audio_ext_parse(const struct spa_pod *format, struct spa_audio_info *info, size_t size) { int res; + uint32_t media_type, media_subtype; - if ((res = spa_format_parse(format, &info->media_type, &info->media_subtype)) < 0) + if ((res = spa_format_parse(format, &media_type, &media_subtype)) < 0) return res; - if (info->media_type != SPA_MEDIA_TYPE_audio) + if (media_type != SPA_MEDIA_TYPE_audio) return -EINVAL; - switch (info->media_subtype) { + if (!spa_format_audio_ext_valid_size(media_subtype, size)) + return -EINVAL; + + info->media_type = media_type; + info->media_subtype = media_subtype; + + switch (media_subtype) { case SPA_MEDIA_SUBTYPE_raw: return spa_format_audio_raw_ext_parse(format, &info->info.raw, size - offsetof(struct spa_audio_info, info.raw)); @@ -109,6 +149,11 @@ SPA_API_AUDIO_FORMAT_UTILS struct spa_pod * spa_format_audio_ext_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info *info, size_t size) { + if (!spa_format_audio_ext_valid_size(info->media_subtype, size)) { + errno = EINVAL; + return NULL; + } + switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: return spa_format_audio_raw_ext_build(builder, id, &info->info.raw, diff --git a/spa/include/spa/param/audio/format.h b/spa/include/spa/param/audio/format.h index ac9b10dda..6e7a71f6c 100644 --- a/spa/include/spa/param/audio/format.h +++ b/spa/include/spa/param/audio/format.h @@ -59,6 +59,8 @@ struct spa_audio_info { struct spa_audio_info_dts dts; struct spa_audio_info_mpegh mpegh; } info; + + /* padding follows here when info has flexible size */ }; /** diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index e83c4495b..38fcb449c 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -60,6 +60,10 @@ spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, { uint32_t v; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) + return -EINVAL; + if (spa_streq(key, SPA_KEY_AUDIO_FORMAT)) { if (force || info->format == 0) info->format = (enum spa_audio_format)spa_type_audio_format_from_short_name(val); @@ -100,6 +104,9 @@ spa_audio_info_raw_ext_init_dict_keys_va(struct spa_audio_info_raw *info, size_t { int res; + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) + return -EINVAL; + memset(info, 0, size); SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); if (dict) { diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index ce5e61e67..3bb94eaa3 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -35,6 +35,9 @@ spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_in int res; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) + return -EINVAL; + info->flags = 0; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, @@ -64,6 +67,11 @@ spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id, struct spa_pod_frame f; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) { + errno = EINVAL; + return NULL; + } + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 392b65daa..bcc0a122d 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -293,6 +293,8 @@ struct spa_audio_info_raw { #define SPA_AUDIO_INFO_RAW_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_info_raw,position))/sizeof(uint32_t)) +#define SPA_AUDIO_INFO_RAW_VALID_SIZE(size) ((size) >= offsetof(struct spa_audio_info_raw, position)) + #define SPA_KEY_AUDIO_FORMAT "audio.format" /**< an audio format as string, * Ex. "S16LE" */ diff --git a/test/meson.build b/test/meson.build index ad658bcc6..5e38db383 100644 --- a/test/meson.build +++ b/test/meson.build @@ -112,6 +112,7 @@ test('test-spa', executable('test-spa', 'test-spa-buffer.c', 'test-spa-control.c', + 'test-spa-format.c', 'test-spa-json.c', 'test-spa-utils.c', 'test-spa-log.c', diff --git a/test/test-spa-format.c b/test/test-spa-format.c new file mode 100644 index 000000000..7cbec7690 --- /dev/null +++ b/test/test-spa-format.c @@ -0,0 +1,129 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#include +#include + +#include "pwtest.h" + +PWTEST(audio_format_sizes) +{ + union { + uint8_t buf[1024]; + struct spa_audio_info align; + } data; + struct spa_audio_info info; + size_t i; + + memset(&info, 0xf3, sizeof(info)); + info.media_type = SPA_MEDIA_TYPE_audio; + info.media_subtype = SPA_MEDIA_SUBTYPE_raw; + info.info.raw.channels = 5; + info.info.raw.format = SPA_AUDIO_FORMAT_F32P; + info.info.raw.rate = 12345; + info.info.raw.flags = 0; + info.info.raw.position[0] = 1; + info.info.raw.position[1] = 2; + info.info.raw.position[2] = 3; + info.info.raw.position[3] = 4; + info.info.raw.position[4] = 5; + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + memcpy(data.buf, &info, sizeof(info)); + + pod = spa_format_audio_ext_build(&b, 123, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + offsetof(struct spa_audio_info_raw, position)) + pwtest_bool_true(!pod); + else + pwtest_bool_true(pod); + } + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + int ret; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + pod = spa_format_audio_ext_build(&b, 123, &info, sizeof(info)); + pwtest_bool_true(pod); + + memset(data.buf, 0xf3, sizeof(data.buf)); + + ret = spa_format_audio_ext_parse(pod, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + offsetof(struct spa_audio_info_raw, position) + + info.info.raw.channels*sizeof(uint32_t)) { + for (size_t j = i; j < sizeof(data.buf); ++j) + pwtest_int_eq(data.buf[j], 0xf3); + pwtest_int_lt(ret, 0); + } else { + pwtest_int_ge(ret, 0); + pwtest_bool_true(memcmp(data.buf, &info, SPA_MIN(i, sizeof(info))) == 0); + } + } + + memset(&info, 0xf3, sizeof(info)); + info.media_type = SPA_MEDIA_TYPE_audio; + info.media_subtype = SPA_MEDIA_SUBTYPE_aac; + info.info.aac.rate = 12345; + info.info.aac.channels = 6; + info.info.aac.bitrate = 54321; + info.info.aac.stream_format = SPA_AUDIO_AAC_STREAM_FORMAT_MP4LATM; + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + memcpy(data.buf, &info, sizeof(info)); + + pod = spa_format_audio_ext_build(&b, 123, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + sizeof(struct spa_audio_info_aac)) + pwtest_bool_true(!pod); + else + pwtest_bool_true(pod); + } + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + int ret; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + pod = spa_format_audio_ext_build(&b, 123, &info, sizeof(info)); + pwtest_bool_true(pod); + + memset(data.buf, 0xf3, sizeof(data.buf)); + + ret = spa_format_audio_ext_parse(pod, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + sizeof(struct spa_audio_info_aac)) { + for (size_t j = i; j < sizeof(data.buf); ++j) + pwtest_int_eq(data.buf[j], 0xf3); + pwtest_int_lt(ret, 0); + } else { + pwtest_int_ge(ret, 0); + pwtest_bool_true(memcmp(data.buf, &info, SPA_MIN(i, sizeof(info))) == 0); + } + } + + return PWTEST_PASS; +} + +PWTEST_SUITE(spa_format) +{ + pwtest_add(audio_format_sizes, PWTEST_NOARG); + + return PWTEST_PASS; +} From 614186a59076e8c857eaf7d94702b20e5f5030e9 Mon Sep 17 00:00:00 2001 From: Jonas Holmberg Date: Wed, 22 Oct 2025 13:50:24 +0200 Subject: [PATCH 27/31] module-echo-cancel: Sync capture and sink buffers Call process() when capture and sink ringbuffers contain data from the same graph cycle and only process the latest block from them to avoid adding latency that can accumulate if one of the streams gets more than one buffer before the other gets its first buffer when starting up. --- src/modules/module-echo-cancel.c | 92 +++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 997506e6f..037509c35 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -229,8 +229,10 @@ struct impl { struct spa_audio_aec *aec; uint32_t aec_blocksize; - unsigned int capture_ready:1; - unsigned int sink_ready:1; + struct spa_io_position *capture_position; + struct spa_io_position *sink_position; + uint32_t capture_cycle; + uint32_t sink_cycle; unsigned int do_disconnect:1; @@ -309,11 +311,17 @@ static void process(struct impl *impl) struct spa_data *dd; uint32_t i, size; uint32_t rindex, pindex, oindex, pdindex, avail; + int32_t pavail, pdavail; size = impl->aec_blocksize; - /* First read a block from the playback and capture ring buffers */ - spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); + /* First read a block from the capture ring buffer */ + avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); + while (avail > size) { + /* drop samples from previous graph cycles */ + spa_ringbuffer_read_update(&impl->rec_ring, rindex + size); + avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); + } for (i = 0; i < impl->rec_info.channels; i++) { /* captured samples, with echo from sink */ @@ -331,19 +339,30 @@ static void process(struct impl *impl) out[i] = &out_buf[i][0]; } - spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); - spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); + pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); + pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); if (impl->playback != NULL && (pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) { pw_log_debug("out of playback buffers: %m"); /* playback stream may not yet be in streaming state, drop play * data to avoid introducing additional playback latency */ - spa_ringbuffer_read_update(&impl->play_ring, pindex + size); - spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); + spa_ringbuffer_read_update(&impl->play_ring, pindex + pavail); + spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + pdavail); goto done; } + while (pavail > size) { + /* drop samples from previous graph cycles */ + spa_ringbuffer_read_update(&impl->play_ring, pindex + size); + pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); + } + while (pdavail > size) { + /* drop samples from previous graph cycles */ + spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); + pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); + } + for (i = 0; i < impl->play_info.channels; i++) { /* echo from sink */ play[i] = &play_buf[i][0]; @@ -454,8 +473,8 @@ static void process(struct impl *impl) } done: - impl->sink_ready = false; - impl->capture_ready = false; + impl->capture_cycle = 0; + impl->sink_cycle = 0; } static void reset_buffers(struct impl *impl) @@ -479,8 +498,8 @@ static void reset_buffers(struct impl *impl) spa_ringbuffer_get_read_index(&impl->play_ring, &index); spa_ringbuffer_read_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); - impl->sink_ready = false; - impl->capture_ready = false; + impl->capture_cycle = 0; + impl->sink_cycle = 0; } static void capture_destroy(void *d) @@ -546,8 +565,11 @@ static void capture_process(void *data) spa_ringbuffer_write_update(&impl->rec_ring, index + size); if (avail + size >= impl->aec_blocksize) { - impl->capture_ready = true; - if (impl->sink_ready) + if (impl->capture_position) + impl->capture_cycle = impl->capture_position->clock.cycle; + else + pw_log_warn("no capture position"); + if (impl->capture_cycle == impl->sink_cycle) process(impl); } @@ -740,12 +762,26 @@ static void input_param_changed(void *data, uint32_t id, const struct spa_pod* p } } +static void capture_io_changed(void *data, uint32_t id, void *area, uint32_t size) +{ + struct impl *impl = data; + + switch (id) { + case SPA_IO_Position: + impl->capture_position = area; + break; + default: + break; + } +} + static const struct pw_stream_events capture_events = { PW_VERSION_STREAM_EVENTS, .destroy = capture_destroy, .state_changed = capture_state_changed, .process = capture_process, - .param_changed = input_param_changed + .param_changed = input_param_changed, + .io_changed = capture_io_changed }; static void source_destroy(void *d) @@ -930,10 +966,15 @@ static void sink_process(void *data) SPA_PTROFF(d->data, offs, void), size); } spa_ringbuffer_write_update(&impl->play_ring, index + size); + spa_ringbuffer_get_write_index(&impl->play_delayed_ring, &index); + spa_ringbuffer_write_update(&impl->play_delayed_ring, index + size); if (avail + size >= impl->aec_blocksize) { - impl->sink_ready = true; - if (impl->capture_ready) + if (impl->sink_position) + impl->sink_cycle = impl->sink_position->clock.cycle; + else + pw_log_warn("no sink position"); + if (impl->capture_cycle == impl->sink_cycle) process(impl); } @@ -955,12 +996,27 @@ static const struct pw_stream_events playback_events = { .state_changed = playback_state_changed, .param_changed = output_param_changed }; + +static void sink_io_changed(void *data, uint32_t id, void *area, uint32_t size) +{ + struct impl *impl = data; + + switch (id) { + case SPA_IO_Position: + impl->sink_position = area; + break; + default: + break; + } +} + static const struct pw_stream_events sink_events = { PW_VERSION_STREAM_EVENTS, .destroy = sink_destroy, .process = sink_process, .state_changed = sink_state_changed, - .param_changed = output_param_changed + .param_changed = output_param_changed, + .io_changed = sink_io_changed }; #define MAX_PARAMS 512u From 0276bb5b063d8b34a55b1292c4a01c1f7b5c9cc4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 27 Oct 2025 11:43:04 +0100 Subject: [PATCH 28/31] modules: ringbuffer avail is signed --- src/modules/module-echo-cancel.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 037509c35..a4cdd2435 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -309,15 +309,15 @@ static void process(struct impl *impl) const float *play_delayed[impl->play_info.channels]; float *out[impl->out_info.channels]; struct spa_data *dd; - uint32_t i, size; - uint32_t rindex, pindex, oindex, pdindex, avail; - int32_t pavail, pdavail; + uint32_t i; + uint32_t rindex, pindex, oindex, pdindex, size; + int32_t avail, pavail, pdavail; size = impl->aec_blocksize; /* First read a block from the capture ring buffer */ avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); - while (avail > size) { + while (avail > (int32_t)size) { /* drop samples from previous graph cycles */ spa_ringbuffer_read_update(&impl->rec_ring, rindex + size); avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); @@ -352,12 +352,12 @@ static void process(struct impl *impl) goto done; } - while (pavail > size) { + while (pavail > (int32_t)size) { /* drop samples from previous graph cycles */ spa_ringbuffer_read_update(&impl->play_ring, pindex + size); pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); } - while (pdavail > size) { + while (pdavail > (int32_t)size) { /* drop samples from previous graph cycles */ spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); @@ -450,7 +450,7 @@ static void process(struct impl *impl) * available on the source */ avail = spa_ringbuffer_get_read_index(&impl->out_ring, &oindex); - while (avail >= size) { + while (avail >= (int32_t)size) { if ((cout = pw_stream_dequeue_buffer(impl->source)) != NULL) { for (i = 0; i < impl->out_info.channels; i++) { dd = &cout->buffer->datas[i]; From 94d0d8bc095b001f36f4e27c23d22c7ce4aca8ca Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 27 Oct 2025 13:32:03 +0100 Subject: [PATCH 29/31] spa: add spa_json_init_relax spa_json_init assumes that we start in an object and always requires a key/value pair. If the last part is a key, it returns and error and does not want to return the key value. This causes problems when parsing AUX0,AUX1,AUX2 or any relaxed array withand odd number of elements. Make a new spa_json_init_relax that takes the type of the container we're assuming we're in and set the state of the parser to array when we are parsing a relaxed array. Fixes #4944 --- spa/include/spa/utils/json-core.h | 9 +++++++++ spa/include/spa/utils/json.h | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/utils/json-core.h b/spa/include/spa/utils/json-core.h index 800763571..5616bffe1 100644 --- a/spa/include/spa/utils/json-core.h +++ b/spa/include/spa/utils/json-core.h @@ -54,6 +54,15 @@ SPA_API_JSON void spa_json_init(struct spa_json * iter, const char *data, size_t { *iter = SPA_JSON_INIT(data, size); } + +#define SPA_JSON_INIT_RELAX(type,data,size) \ + ((struct spa_json) { (data), (data)+(size), NULL, (uint32_t)((type) == '[' ? 0x10 : 0x0), 0 }) + +SPA_API_JSON void spa_json_init_relax(struct spa_json * iter, char type, const char *data, size_t size) +{ + *iter = SPA_JSON_INIT_RELAX(type, data, size); +} + #define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), (iter)->state & 0xff0, 0 }) SPA_API_JSON void spa_json_enter(struct spa_json * iter, struct spa_json * sub) diff --git a/spa/include/spa/utils/json.h b/spa/include/spa/utils/json.h index c8030345e..212637dab 100644 --- a/spa/include/spa/utils/json.h +++ b/spa/include/spa/utils/json.h @@ -105,7 +105,7 @@ SPA_API_JSON_UTILS int spa_json_begin_container(struct spa_json * iter, spa_json_init(iter, data, size); res = spa_json_enter_container(iter, iter, type); if (res == -EPROTO && relax) - spa_json_init(iter, data, size); + spa_json_init_relax(iter, type, data, size); else if (res <= 0) return res; return 1; From 23c449af5d6afc8dde0685d61e6ed11d89b09000 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 27 Oct 2025 14:20:25 +0100 Subject: [PATCH 30/31] test: add test for an array with odd number of items We have to use the relax version to get the expected container type correct. --- test/test-spa-json.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test-spa-json.c b/test/test-spa-json.c index 66ef2eeac..0c3c46f59 100644 --- a/test/test-spa-json.c +++ b/test/test-spa-json.c @@ -609,7 +609,7 @@ static void test_array(const char *str, const char * const vals[]) spa_json_init(&it[0], str, strlen(str)); if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], str, strlen(str)); + spa_json_init_relax(&it[1], '[', str, strlen(str)); for (i = 0; vals[i]; i++) { pwtest_int_gt(spa_json_get_string(&it[1], val, sizeof(val)), 0); pwtest_str_eq(val, vals[i]); @@ -624,6 +624,7 @@ PWTEST(json_array) test_array("[FL FR]", (const char *[]){ "FL", "FR", NULL }); test_array("FL FR", (const char *[]){ "FL", "FR", NULL }); test_array("[ FL FR ]", (const char *[]){ "FL", "FR", NULL }); + test_array("FL FR FC", (const char *[]){ "FL", "FR", "FC", NULL }); return PWTEST_PASS; } From 76a31a47c2cf063abc70c29f42e60fb0fa03dee5 Mon Sep 17 00:00:00 2001 From: Jonas Holmberg Date: Mon, 27 Oct 2025 14:37:03 +0100 Subject: [PATCH 31/31] module-echo-cancel: Avoid discontinuity Keep the samples in the ringbuffer that are needed the next cycle to avoid discontinuity when the aec blocksize is not equal to or divisible by quantum. --- src/modules/module-echo-cancel.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index a4cdd2435..98efa35c5 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -317,10 +317,15 @@ static void process(struct impl *impl) /* First read a block from the capture ring buffer */ avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); - while (avail > (int32_t)size) { - /* drop samples from previous graph cycles */ + while (avail >= (int32_t)size * 2) { + /* drop samples that are not needed this or next cycle. Note + * that samples are kept in the ringbuffer until next cycle if + * size is not equal to or divisible by quantum, to avoid + * discontinuity */ + pw_log_debug("avail %d", avail); spa_ringbuffer_read_update(&impl->rec_ring, rindex + size); avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); + pw_log_debug("new avail %d, size %u", avail, size); } for (i = 0; i < impl->rec_info.channels; i++) { @@ -352,15 +357,19 @@ static void process(struct impl *impl) goto done; } - while (pavail > (int32_t)size) { - /* drop samples from previous graph cycles */ - spa_ringbuffer_read_update(&impl->play_ring, pindex + size); + if (pavail > avail) { + /* drop too old samples from previous graph cycles */ + pw_log_debug("pavail %d, dropping %d", pavail, pavail - avail); + spa_ringbuffer_read_update(&impl->play_ring, pindex + pavail - avail); pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); + pw_log_debug("new pavail %d, avail %d", pavail, avail); } - while (pdavail > (int32_t)size) { - /* drop samples from previous graph cycles */ - spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); + if (pdavail > avail) { + /* drop too old samples from previous graph cycles */ + pw_log_debug("pdavail %d, dropping %d", pdavail, pdavail - avail); + spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + pdavail - avail); pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); + pw_log_debug("new pdavail %d, avail %d", pdavail, avail); } for (i = 0; i < impl->play_info.channels; i++) {