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<N> will make <N> AUX
channels. Easier to say AUX128 than to write an array with the
128 AUX channels.
This commit is contained in:
Wim Taymans 2025-10-30 11:35:03 +01:00
parent 056f257058
commit 8ba08f3029
7 changed files with 151 additions and 0 deletions

View file

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

View file

@ -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 <spa/utils/type.h>
#include <spa/utils/string.h>
#include <spa/param/audio/layout.h>
#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 */

View file

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

View file

@ -9,6 +9,7 @@
#include <spa/utils/json.h>
#include <spa/param/audio/raw.h>
#include <spa/param/audio/raw-types.h>
#include <spa/param/audio/layout-types.h>
#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,

View file

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

View file

@ -6,6 +6,7 @@
#define SPA_AUDIO_TYPES_H
#include <spa/param/audio/raw-types.h>
#include <spa/param/audio/layout-types.h>
#include <spa/param/audio/iec958-types.h>
#include <spa/param/audio/mp3-types.h>
#include <spa/param/audio/aac-types.h>

View file

@ -63,6 +63,7 @@
#include <spa/param/audio/iec958-types.h>
#include <spa/param/audio/iec958-utils.h>
#include <spa/param/audio/layout.h>
#include <spa/param/audio/layout-types.h>
#include <spa/param/audio/mp3.h>
#include <spa/param/audio/mp3-types.h>
#include <spa/param/audio/mp3-utils.h>