From 8ba08f3029dab9bd947f11af6f37212ebaf5ce88 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 30 Oct 2025 11:35:03 +0100 Subject: [PATCH] spa: add audio.layout property Makes it possible to define audio channels and position with a predefined layout string. It is easier and less error prone to say "5.1" than to spell out [ FL FR FC LFE RL RR ]. AUX channels have a special syntax. AUX will make AUX channels. Easier to say AUX128 than to write an array with the 128 AUX channels. --- doc/dox/config/pipewire-props.7.md | 3 + spa/include/spa/param/audio/layout-types.h | 118 +++++++++++++++++++++ spa/include/spa/param/audio/layout.h | 3 + spa/include/spa/param/audio/raw-json.h | 24 +++++ spa/include/spa/param/audio/raw.h | 1 + spa/include/spa/param/audio/type-info.h | 1 + spa/lib/lib.c | 1 + 7 files changed, 151 insertions(+) create mode 100644 spa/include/spa/param/audio/layout-types.h diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index 97b758a2a..964ebec0d 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -843,6 +843,9 @@ The audio format to open the device in. By default this is "UNKNOWN", which will @PAR@ node-prop audio.position # JSON array of strings The audio position of the channels in the device. This is auto detected based on the profile. You can configure an array of channel positions, like "[ FL, FR ]". +@PAR@ node-prop audio.layout # string +The audio layout of the channels in the device. You can use any of the predefined layouts, like "Stereo", "5.1" etc. + @PAR@ node-prop audio.allowed-rates # JSON array of integers \parblock The allowed audio rates to open the device with. Default is "[ ]", which means the device can be opened in any supported rate. diff --git a/spa/include/spa/param/audio/layout-types.h b/spa/include/spa/param/audio/layout-types.h new file mode 100644 index 000000000..c8c78af39 --- /dev/null +++ b/spa/include/spa/param/audio/layout-types.h @@ -0,0 +1,118 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_LAYOUT_TYPES_H +#define SPA_AUDIO_LAYOUT_TYPES_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup spa_param + * \{ + */ + +#ifndef SPA_API_AUDIO_LAYOUT_TYPES + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_LAYOUT_TYPES SPA_API_IMPL + #else + #define SPA_API_AUDIO_LAYOUT_TYPES static inline + #endif +#endif + +static const struct spa_type_audio_layout_info { + const char *name; + struct spa_audio_layout_info layout; +} spa_type_audio_layout_info[] = { + { "Mono", { SPA_AUDIO_LAYOUT_Mono } }, + { "Stereo", { SPA_AUDIO_LAYOUT_Stereo } }, + { "Quad", { SPA_AUDIO_LAYOUT_Quad } }, + { "Pentagonal", { SPA_AUDIO_LAYOUT_Pentagonal } }, + { "Hexagonal", { SPA_AUDIO_LAYOUT_Hexagonal } }, + { "Octagonal", { SPA_AUDIO_LAYOUT_Octagonal } }, + { "Cube", { SPA_AUDIO_LAYOUT_Cube } }, + { "MPEG-1.0", { SPA_AUDIO_LAYOUT_MPEG_1_0 } }, + { "MPEG-2.0", { SPA_AUDIO_LAYOUT_MPEG_2_0 } }, + { "MPEG-3.0A", { SPA_AUDIO_LAYOUT_MPEG_3_0A } }, + { "MPEG-3.0B", { SPA_AUDIO_LAYOUT_MPEG_3_0B } }, + { "MPEG-4.0A", { SPA_AUDIO_LAYOUT_MPEG_4_0A } }, + { "MPEG-4.0B", { SPA_AUDIO_LAYOUT_MPEG_4_0B } }, + { "MPEG-5.0A", { SPA_AUDIO_LAYOUT_MPEG_5_0A } }, + { "MPEG-5.0B", { SPA_AUDIO_LAYOUT_MPEG_5_0B } }, + { "MPEG-5.0C", { SPA_AUDIO_LAYOUT_MPEG_5_0C } }, + { "MPEG-5.0D", { SPA_AUDIO_LAYOUT_MPEG_5_0D } }, + { "MPEG-5.1A", { SPA_AUDIO_LAYOUT_MPEG_5_1A } }, + { "MPEG-5.1B", { SPA_AUDIO_LAYOUT_MPEG_5_1B } }, + { "MPEG-5.1C", { SPA_AUDIO_LAYOUT_MPEG_5_1C } }, + { "MPEG-5.1D", { SPA_AUDIO_LAYOUT_MPEG_5_1D } }, + { "MPEG-6.1A", { SPA_AUDIO_LAYOUT_MPEG_6_1A } }, + { "MPEG-7.1A", { SPA_AUDIO_LAYOUT_MPEG_7_1A } }, + { "MPEG-7.1B", { SPA_AUDIO_LAYOUT_MPEG_7_1B } }, + { "MPEG-7.1C", { SPA_AUDIO_LAYOUT_MPEG_7_1C } }, + { "2.1", { SPA_AUDIO_LAYOUT_2_1 } }, + { "2RC", { SPA_AUDIO_LAYOUT_2RC } }, + { "2FC", { SPA_AUDIO_LAYOUT_2FC } }, + { "3.1", { SPA_AUDIO_LAYOUT_3_1 } }, + { "4.0", { SPA_AUDIO_LAYOUT_4_0 } }, + { "2.2", { SPA_AUDIO_LAYOUT_2_2 } }, + { "4.1", { SPA_AUDIO_LAYOUT_4_1 } }, + { "5.0", { SPA_AUDIO_LAYOUT_5_0 } }, + { "5.0R", { SPA_AUDIO_LAYOUT_5_0R } }, + { "5.1", { SPA_AUDIO_LAYOUT_5_1 } }, + { "5.1R", { SPA_AUDIO_LAYOUT_5_1R } }, + { "6.0", { SPA_AUDIO_LAYOUT_6_0 } }, + { "6.0F", { SPA_AUDIO_LAYOUT_6_0F } }, + { "6.1", { SPA_AUDIO_LAYOUT_6_1 } }, + { "6.1F", { SPA_AUDIO_LAYOUT_6_1F } }, + { "7.0", { SPA_AUDIO_LAYOUT_7_0 } }, + { "7.0F", { SPA_AUDIO_LAYOUT_7_0F } }, + { "7.1", { SPA_AUDIO_LAYOUT_7_1 } }, + { "7.1W", { SPA_AUDIO_LAYOUT_7_1W } }, + { "7.1WR", { SPA_AUDIO_LAYOUT_7_1WR } }, + { NULL, }, +}; + +SPA_API_AUDIO_LAYOUT_TYPES int +spa_audio_layout_info_parse_name(struct spa_audio_layout_info *layout, size_t size, + const char *name) +{ + uint32_t max_position = SPA_AUDIO_LAYOUT_INFO_MAX_POSITION(size); + if (spa_strstartswith(name, "AUX")) { + uint32_t i, n_pos; + if (spa_atou32(name+3, &n_pos, 10)) { + if (n_pos > max_position) + return -ECHRNG; + for (i = 0; i < 0x1000 && i < n_pos; i++) + layout->position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + for (; i < n_pos; i++) + layout->position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; + layout->n_channels = n_pos; + return n_pos; + } + } + SPA_FOR_EACH_ELEMENT_VAR(spa_type_audio_layout_info, i) { + if (spa_streq(name, i->name)) { + if (i->layout.n_channels > max_position) + return -ECHRNG; + *layout = i->layout; + return i->layout.n_channels; + } + } + return -ENOTSUP; +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_LAYOUT_TYPES_H */ diff --git a/spa/include/spa/param/audio/layout.h b/spa/include/spa/param/audio/layout.h index 6d6ec4898..2293d41f1 100644 --- a/spa/include/spa/param/audio/layout.h +++ b/spa/include/spa/param/audio/layout.h @@ -21,8 +21,11 @@ extern "C" { struct spa_audio_layout_info { uint32_t n_channels; uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + /* padding may follow to allow more channels */ }; +#define SPA_AUDIO_LAYOUT_INFO_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_layout_info,position))/sizeof(uint32_t)) + #define SPA_AUDIO_LAYOUT_Mono 1, { SPA_AUDIO_CHANNEL_MONO, } #define SPA_AUDIO_LAYOUT_Stereo 2, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, } #define SPA_AUDIO_LAYOUT_Quad 4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index 38fcb449c..6b1b25164 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -9,6 +9,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -54,6 +55,20 @@ spa_audio_parse_position(const char *str, size_t len, return spa_audio_parse_position_n(str, len, position, SPA_AUDIO_MAX_CHANNELS, n_channels); } +SPA_API_AUDIO_RAW_JSON int +spa_audio_parse_layout(const char *str, uint32_t *position, uint32_t max_position, + uint32_t *n_channels) +{ + struct spa_audio_layout_info l; + uint32_t i; + if (spa_audio_layout_info_parse_name(&l, sizeof(l), str) <= 0) + return 0; + for (i = 0; i < l.n_channels && i < max_position; i++) + position[i] = l.position[i]; + *n_channels = l.n_channels; + return l.n_channels; +} + SPA_API_AUDIO_RAW_JSON int spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, const char *key, const char *val, bool force) @@ -76,6 +91,15 @@ spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, return -ECHRNG; info->channels = v; } + } else if (spa_streq(key, SPA_KEY_AUDIO_LAYOUT)) { + if (force || info->channels == 0) { + if (spa_audio_parse_layout(val, info->position, max_position, &v) > 0) { + if (v > max_position) + return -ECHRNG; + info->channels = v; + SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); + } + } } 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, diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index bcc0a122d..8500c4f17 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -302,6 +302,7 @@ struct spa_audio_info_raw { * Ex. "FL" */ #define SPA_KEY_AUDIO_CHANNELS "audio.channels" /**< an audio channel count as int */ #define SPA_KEY_AUDIO_RATE "audio.rate" /**< an audio sample rate as int */ +#define SPA_KEY_AUDIO_LAYOUT "audio.layout" /**< channel positions as predefined layout */ #define SPA_KEY_AUDIO_POSITION "audio.position" /**< channel positions as comma separated list * of channels ex. "FL,FR" */ #define SPA_KEY_AUDIO_ALLOWED_RATES "audio.allowed-rates" /**< a list of allowed samplerates diff --git a/spa/include/spa/param/audio/type-info.h b/spa/include/spa/param/audio/type-info.h index 1a15ad3dd..3c5d6f4c7 100644 --- a/spa/include/spa/param/audio/type-info.h +++ b/spa/include/spa/param/audio/type-info.h @@ -6,6 +6,7 @@ #define SPA_AUDIO_TYPES_H #include +#include #include #include #include diff --git a/spa/lib/lib.c b/spa/lib/lib.c index 30aa91194..0aa35fae3 100644 --- a/spa/lib/lib.c +++ b/spa/lib/lib.c @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include