Compare commits

...

32 commits

Author SHA1 Message Date
Hans de Goede
1de07a6a31 Merge branch 'spa-v4l2-log-fix' into 'master'
spa: v4l2: Add missing v4l2_log_topic_init() call to device/udev impl_init()

See merge request pipewire/pipewire!2117
2025-10-27 18:41:05 +01:00
Jonas Holmberg
76a31a47c2 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.
2025-10-27 14:39:29 +01:00
Wim Taymans
23c449af5d 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.
2025-10-27 14:20:25 +01:00
Wim Taymans
94d0d8bc09 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
2025-10-27 13:32:03 +01:00
Wim Taymans
0276bb5b06 modules: ringbuffer avail is signed 2025-10-27 11:43:04 +01:00
Jonas Holmberg
614186a590 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.
2025-10-27 08:43:08 +01:00
Pauli Virtanen
c6d0b364ab 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.
2025-10-26 18:23:17 +02:00
Pauli Virtanen
8a23b13798 spa: param: pass correct struct size to spa_format_audio_raw_ext_parse/build 2025-10-26 17:44:03 +02:00
Pauli Virtanen
3d08c0557f properties: fix assign + conditional expression 2025-10-26 14:12:19 +00:00
Pauli Virtanen
68dc45cc62 audioconvert: simplify volume ramp generation
Don't use floating point accumulators, interpolate from sample position.
2025-10-26 14:12:19 +00:00
Pauli Virtanen
b0e308e0dc spa: examples: fix getopt usage + typos in adapter-control 2025-10-26 14:12:19 +00:00
Pauli Virtanen
fe2c62b9b1 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
2025-10-26 14:12:19 +00:00
Pauli Virtanen
3febf09b85 alsa: fix typoed braces in condition + assign 2025-10-26 14:12:19 +00:00
Pauli Virtanen
93495d3a75 spa: param: infer raw audio channels from position if unset
The behavior before b8eeb2db45 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.
2025-10-26 15:47:48 +02:00
Pauli Virtanen
9f1a149876 ci: add file package, for coverity
Try to fix coverity by adding missing 'file' package to container
2025-10-25 12:42:45 +03:00
Wim Taymans
88c65932d8 acp: use global max channels if defined 2025-10-24 17:16:03 +02:00
Wim Taymans
c8d4de5e77 acp: bump max channels to 128 2025-10-24 17:00:42 +02:00
Wim Taymans
c4244a6cf3 string: use spa_strbuf instead of snprintf magic 2025-10-24 17:00:11 +02:00
Wim Taymans
f7c3d37969 fmt-ops: allocate shaper memory dynamically
It is based on the number of channels so allocate it dynamically.
2025-10-24 12:46:38 +02:00
Wim Taymans
d18670d7bb pw-cat: improve channel checks
Make sure we don't use too many channels.
2025-10-24 10:42:05 +02:00
Wim Taymans
aa0272f6f3 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.
2025-10-24 10:31:45 +02:00
Wim Taymans
78219471ff 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.
2025-10-24 09:35:59 +02:00
Wim Taymans
6d74eee874 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.
2025-10-24 08:53:21 +02:00
Wim Taymans
be29ae4ef6 audioadapter: add some more debug info when parsing fails 2025-10-23 18:05:22 +02:00
Wim Taymans
5e1e3fca1e modules: handle format parsing errors 2025-10-23 18:01:35 +02:00
Wim Taymans
b8eeb2db45 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.
2025-10-23 17:59:51 +02:00
Wim Taymans
c5533b3c32 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.
2025-10-22 13:26:52 +02:00
Wim Taymans
11f1298f53 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.
2025-10-22 13:04:53 +02:00
Wim Taymans
7177f8269d bluez: use function to get the channel position from a name 2025-10-22 12:54:30 +02:00
Wim Taymans
6465a63bf6 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.
2025-10-22 12:47:00 +02:00
Wim Taymans
ae50bb5dc0 audio: don't limit channels to max positions
We can have more channels than we have positions.
2025-10-22 09:39:15 +02:00
Hans de Goede
e48a1595a6 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 <hdegoede@redhat.com>
2024-09-10 17:42:26 +02:00
61 changed files with 828 additions and 439 deletions

View file

@ -38,7 +38,7 @@ include:
.fedora: .fedora:
variables: variables:
# Update this tag when you want to trigger a rebuild # 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_VERSION: '42'
FDO_DISTRIBUTION_PACKAGES: >- FDO_DISTRIBUTION_PACKAGES: >-
alsa-lib-devel alsa-lib-devel
@ -48,6 +48,7 @@ include:
dbus-devel dbus-devel
doxygen doxygen
fdk-aac-free-devel fdk-aac-free-devel
file
findutils findutils
gcc gcc
gcc-c++ gcc-c++

View file

@ -115,10 +115,11 @@ cc_flags = common_flags + [
'-Werror=old-style-definition', '-Werror=old-style-definition',
'-Werror=missing-parameter-type', '-Werror=missing-parameter-type',
'-Werror=strict-prototypes', '-Werror=strict-prototypes',
'-DSPA_AUDIO_MAX_CHANNELS=128u',
] ]
add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c') add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c')
add_project_arguments(cc_native.get_supported_arguments(cc_flags),
cc_flags_native = cc_native.get_supported_arguments(cc_flags) language: 'c', native: true)
have_cpp = add_languages('cpp', native: false, required : false) have_cpp = add_languages('cpp', native: false, required : false)

View file

@ -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 #define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE
#endif #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) { switch (channels) {
case 8: case 8:
position[6] = SPA_AUDIO_CHANNEL_SL; 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: case SPA_MEDIA_SUBTYPE_raw:
pw->requested.info.raw.channels = io->channels; pw->requested.info.raw.channels = io->channels;
pw->requested.info.raw.rate = io->rate; pw->requested.info.raw.rate = io->rate;
set_default_channels(io->channels, pw->requested.info.raw.position, set_default_channels(io->channels, pw->requested.info.raw.position);
SPA_N_ELEMENTS(pw->requested.info.raw.position));
fmt_str = spa_type_audio_format_to_short_name(pw->requested.info.raw.format); fmt_str = spa_type_audio_format_to_short_name(pw->requested.info.raw.format);
pw->format = pw->requested; pw->format = pw->requested;
break; 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.bitorder = SPA_PARAM_BITORDER_msb;
pw->requested.info.dsd.channels = io->channels; pw->requested.info.dsd.channels = io->channels;
pw->requested.info.dsd.rate = io->rate * SPA_ABS(pw->requested.info.dsd.interleave); pw->requested.info.dsd.rate = io->rate * SPA_ABS(pw->requested.info.dsd.interleave);
set_default_channels(io->channels, pw->requested.info.dsd.position, set_default_channels(io->channels, pw->requested.info.dsd.position);
SPA_N_ELEMENTS(pw->requested.info.dsd.position));
pw->format = pw->requested; pw->format = pw->requested;
/* we need to let the server decide these values */ /* we need to let the server decide these values */
pw->format.info.dsd.bitorder = 0; pw->format.info.dsd.bitorder = 0;
@ -907,29 +902,30 @@ static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io,
{ {
snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_pipewire_t *pw = io->private_data;
unsigned int i; unsigned int i;
uint32_t *position, max_position; uint32_t *position;
switch (pw->requested.media_subtype) { switch (pw->requested.media_subtype) {
case SPA_MEDIA_SUBTYPE_raw: case SPA_MEDIA_SUBTYPE_raw:
pw->requested.info.raw.channels = map->channels; pw->requested.info.raw.channels = map->channels;
position = pw->requested.info.raw.position; position = pw->requested.info.raw.position;
max_position = SPA_N_ELEMENTS(pw->requested.info.raw.position);
break; break;
case SPA_MEDIA_SUBTYPE_dsd: case SPA_MEDIA_SUBTYPE_dsd:
pw->requested.info.dsd.channels = map->channels; pw->requested.info.dsd.channels = map->channels;
position = pw->requested.info.dsd.position; position = pw->requested.info.dsd.position;
max_position = SPA_N_ELEMENTS(pw->requested.info.dsd.position);
break; break;
default: default:
return -EINVAL; return -EINVAL;
} }
if (map->channels > MAX_CHANNELS)
return -ENOTSUP;
for (i = 0; i < map->channels; i++) { 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] = chmap_to_channel(map->pos[i]);
position[i] = pos;
pw_log_debug("map %d: %s / %s", i, pw_log_debug("map %d: %s / %s", i,
snd_pcm_chmap_name(map->pos[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(position[i],
buf, sizeof(buf), "UNK"));
} }
return 1; return 1;
} }
@ -938,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_pipewire_t *pw = io->private_data;
snd_pcm_chmap_t *map; snd_pcm_chmap_t *map;
uint32_t i, channels, *position, max_position; uint32_t i, channels, *position;
switch (pw->requested.media_subtype) { switch (pw->requested.media_subtype) {
case SPA_MEDIA_SUBTYPE_raw: case SPA_MEDIA_SUBTYPE_raw:
channels = pw->requested.info.raw.channels; channels = pw->requested.info.raw.channels;
position = pw->requested.info.raw.position; position = pw->requested.info.raw.position;
max_position = SPA_N_ELEMENTS(pw->requested.info.raw.position);
break; break;
case SPA_MEDIA_SUBTYPE_dsd: case SPA_MEDIA_SUBTYPE_dsd:
channels = pw->requested.info.dsd.channels; channels = pw->requested.info.dsd.channels;
position = pw->requested.info.dsd.position; position = pw->requested.info.dsd.position;
max_position = SPA_N_ELEMENTS(pw->requested.info.dsd.position);
break; break;
default: default:
return NULL; return NULL;
@ -959,7 +953,7 @@ static snd_pcm_chmap_t * snd_pcm_pipewire_get_chmap(snd_pcm_ioplug_t * io)
channels * sizeof(unsigned int)); channels * sizeof(unsigned int));
map->channels = channels; map->channels = channels;
for (i = 0; i < channels; i++) 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; return map;
} }

View file

@ -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_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_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp),
SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); 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); printf("can't setup source node %d\n", res);
return res; return res;
} }
@ -647,7 +647,7 @@ static int make_nodes(struct data *data)
SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); 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); printf("can't setup sink node %d\n", res);
return res; return res;
} }
@ -987,7 +987,7 @@ int main(int argc, char *argv[])
setlocale(LC_ALL, ""); 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) { switch (c) {
case 'h': case 'h':
show_help(&data, argv[0], false); show_help(&data, argv[0], false);

View file

@ -45,7 +45,7 @@ struct spa_audio_info_dsd {
int32_t interleave; /*< interleave bytes */ int32_t interleave; /*< interleave bytes */
uint32_t rate; /*< sample rate (in bytes per second) */ uint32_t rate; /*< sample rate (in bytes per second) */
uint32_t channels; /*< channels */ 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__ }) #define SPA_AUDIO_INFO_DSD_INIT(...) ((struct spa_audio_info_dsd) { __VA_ARGS__ })

View file

@ -46,20 +46,61 @@ extern "C" {
#endif #endif
#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_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; 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; return res;
if (info->media_type != SPA_MEDIA_TYPE_audio) if (media_type != SPA_MEDIA_TYPE_audio)
return -EINVAL; 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: 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 - offsetof(struct spa_audio_info, info.raw));
case SPA_MEDIA_SUBTYPE_dsp: case SPA_MEDIA_SUBTYPE_dsp:
return spa_format_audio_dsp_parse(format, &info->info.dsp); return spa_format_audio_dsp_parse(format, &info->info.dsp);
case SPA_MEDIA_SUBTYPE_iec958: case SPA_MEDIA_SUBTYPE_iec958:
@ -98,13 +139,25 @@ spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info
return -ENOTSUP; return -ENOTSUP;
} }
SPA_API_AUDIO_FORMAT_UTILS struct spa_pod * SPA_API_AUDIO_FORMAT_UTILS int
spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id, spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info)
const 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_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) { switch (info->media_subtype) {
case SPA_MEDIA_SUBTYPE_raw: 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 - offsetof(struct spa_audio_info, info.raw));
case SPA_MEDIA_SUBTYPE_dsp: case SPA_MEDIA_SUBTYPE_dsp:
return spa_format_audio_dsp_build(builder, id, &info->info.dsp); return spa_format_audio_dsp_build(builder, id, &info->info.dsp);
case SPA_MEDIA_SUBTYPE_iec958: case SPA_MEDIA_SUBTYPE_iec958:
@ -143,6 +196,13 @@ spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id,
errno = ENOTSUP; errno = ENOTSUP;
return NULL; 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));
}
/** /**
* \} * \}
*/ */

View file

@ -59,6 +59,8 @@ struct spa_audio_info {
struct spa_audio_info_dts dts; struct spa_audio_info_dts dts;
struct spa_audio_info_mpegh mpegh; struct spa_audio_info_mpegh mpegh;
} info; } info;
/* padding follows here when info has flexible size */
}; };
/** /**

View file

@ -20,7 +20,7 @@ extern "C" {
struct spa_audio_layout_info { struct spa_audio_layout_info {
uint32_t n_channels; 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, } #define SPA_AUDIO_LAYOUT_Mono 1, { SPA_AUDIO_CHANNEL_MONO, }

View file

@ -29,7 +29,7 @@ extern "C" {
SPA_API_AUDIO_RAW_JSON int SPA_API_AUDIO_RAW_JSON int
spa_audio_parse_position_n(const char *str, size_t len, 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; struct spa_json iter;
char v[256]; char v[256];
@ -38,24 +38,32 @@ spa_audio_parse_position_n(const char *str, size_t len,
if (spa_json_begin_array_relax(&iter, str, len) <= 0) if (spa_json_begin_array_relax(&iter, str, len) <= 0)
return 0; return 0;
while (spa_json_get_string(&iter, v, sizeof(v)) > 0 && while (spa_json_get_string(&iter, v, sizeof(v)) > 0) {
channels < max_channels) { if (channels < max_position)
position[channels++] = spa_type_audio_channel_from_short_name(v); position[channels] = spa_type_audio_channel_from_short_name(v);
channels++;
} }
*n_channels = channels; *n_channels = channels;
return channels; return channels;
} }
SPA_API_AUDIO_RAW_JSON int SPA_API_AUDIO_RAW_JSON int
spa_audio_parse_position(const char *str, size_t len, spa_audio_parse_position(const char *str, size_t len,
uint32_t *position, uint32_t *n_channels) 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 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 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 (spa_streq(key, SPA_KEY_AUDIO_FORMAT)) {
if (force || info->format == 0) if (force || info->format == 0)
info->format = (enum spa_audio_format)spa_type_audio_format_from_short_name(val); info->format = (enum spa_audio_format)spa_type_audio_format_from_short_name(val);
@ -63,42 +71,88 @@ 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)) if (spa_atou32(val, &v, 0) && (force || info->rate == 0))
info->rate = v; info->rate = v;
} else if (spa_streq(key, SPA_KEY_AUDIO_CHANNELS)) { } 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)) {
info->channels = SPA_MIN(v, SPA_N_ELEMENTS(info->position)); if (v > max_position)
return -ECHRNG;
info->channels = v;
}
} else if (spa_streq(key, SPA_KEY_AUDIO_POSITION)) { } else if (spa_streq(key, SPA_KEY_AUDIO_POSITION)) {
if (force || info->channels == 0) { if (force || info->channels == 0) {
if (spa_audio_parse_position_n(val, strlen(val), info->position, 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;
info->channels = v;
SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
}
} }
} }
return 0; 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;
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) {
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_API_AUDIO_RAW_JSON int SPA_SENTINEL
spa_audio_info_raw_init_dict_keys(struct spa_audio_info_raw *info, spa_audio_info_raw_init_dict_keys(struct spa_audio_info_raw *info,
const struct spa_dict *defaults, const struct spa_dict *defaults,
const struct spa_dict *dict, ...) const struct spa_dict *dict, ...)
{ {
spa_zero(*info); va_list args;
SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); int res;
if (dict) { va_start(args, dict);
const char *val, *key; res = spa_audio_info_raw_ext_init_dict_keys_va(info, sizeof(*info), defaults, dict, args);
va_list args; va_end(args);
va_start(args, dict); return res;
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;
} }
/** /**

View file

@ -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) 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) 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"); 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_AudioVolumeRampScale SPA_TYPE_INFO_ENUM_BASE "AudioVolumeRampScale"
#define SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE SPA_TYPE_INFO_AudioVolumeRampScale ":" #define SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE SPA_TYPE_INFO_AudioVolumeRampScale ":"

View file

@ -28,40 +28,16 @@ extern "C" {
#endif #endif
#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_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; struct spa_pod *position = NULL;
int res; 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; info->flags = 0;
res = spa_pod_parse_object(format, res = spa_pod_parse_object(format,
SPA_TYPE_OBJECT_Format, NULL, SPA_TYPE_OBJECT_Format, NULL,
@ -69,18 +45,33 @@ 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_rate, SPA_POD_OPT_Int(&info->rate),
SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels),
SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position));
if (info->channels > max_position)
return -ECHRNG;
if (position == NULL || 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); SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
return res; return res;
} }
SPA_API_AUDIO_RAW_UTILS int
spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info)
{
return spa_format_audio_raw_ext_parse(format, info, sizeof(*info));
}
SPA_API_AUDIO_RAW_UTILS struct spa_pod * SPA_API_AUDIO_RAW_UTILS struct spa_pod *
spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id,
const struct spa_audio_info_raw *info) const struct spa_audio_info_raw *info, size_t size)
{ {
struct spa_pod_frame f; 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_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id);
spa_pod_builder_add(builder, spa_pod_builder_add(builder,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
@ -95,16 +86,25 @@ spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id,
if (info->channels != 0) { if (info->channels != 0) {
spa_pod_builder_add(builder, spa_pod_builder_add(builder,
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0);
if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { /* 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, spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position,
SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id,
SPA_MIN(info->channels, SPA_N_ELEMENTS(info->position)), info->channels, info->position), 0);
info->position), 0);
} }
} }
return (struct spa_pod*)spa_pod_builder_pop(builder, &f); 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));
}
/** /**
* \} * \}
*/ */

View file

@ -18,14 +18,10 @@ extern "C" {
* \{ * \{
*/ */
/* This is the max number of position info, changing this will change the /* This is the max number of channels, changing this will change the
* ABI */ * size of some helper structures. This value should be at least 64 */
#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 */
#ifndef SPA_AUDIO_MAX_CHANNELS #ifndef SPA_AUDIO_MAX_CHANNELS
#define SPA_AUDIO_MAX_CHANNELS 128u #define SPA_AUDIO_MAX_CHANNELS 64u
#endif #endif
enum spa_audio_format { enum spa_audio_format {
@ -279,19 +275,27 @@ enum spa_audio_volume_ramp_scale {
#define SPA_AUDIO_FLAG_NONE (0) /*< no valid flag */ #define SPA_AUDIO_FLAG_NONE (0) /*< no valid flag */
#define SPA_AUDIO_FLAG_UNPOSITIONED (1 << 0) /*< the position array explicitly #define SPA_AUDIO_FLAG_UNPOSITIONED (1 << 0) /*< the position array explicitly
* contains unpositioned channels. */ * 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 { struct spa_audio_info_raw {
enum spa_audio_format format; /*< format, one of enum spa_audio_format */ enum spa_audio_format format; /*< format, one of enum spa_audio_format */
uint32_t flags; /*< extra flags */ uint32_t flags; /*< extra flags */
uint32_t rate; /*< sample rate */ uint32_t rate; /*< sample rate */
uint32_t channels; /*< number of channels. This can be larger than uint32_t channels; /*< number of channels. This can be more than SPA_AUDIO_MAX_CHANNELS
* SPA_AUDIO_MAX_POSITION, the position is taken * and you may assume there is enough padding for the extra
* (index % SPA_AUDIO_MAX_POSITION) */ * channel positions. */
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 */
/* padding follows here when channels > SPA_AUDIO_MAX_CHANNELS */
}; };
#define SPA_AUDIO_INFO_RAW_INIT(...) ((struct spa_audio_info_raw) { __VA_ARGS__ }) #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_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, #define SPA_KEY_AUDIO_FORMAT "audio.format" /**< an audio format as string,
* Ex. "S16LE" */ * Ex. "S16LE" */
#define SPA_KEY_AUDIO_CHANNEL "audio.channel" /**< an audio channel as string, #define SPA_KEY_AUDIO_CHANNEL "audio.channel" /**< an audio channel as string,

View file

@ -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); *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 }) #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) SPA_API_JSON void spa_json_enter(struct spa_json * iter, struct spa_json * sub)

View file

@ -105,7 +105,7 @@ SPA_API_JSON_UTILS int spa_json_begin_container(struct spa_json * iter,
spa_json_init(iter, data, size); spa_json_init(iter, data, size);
res = spa_json_enter_container(iter, iter, type); res = spa_json_enter_container(iter, iter, type);
if (res == -EPROTO && relax) if (res == -EPROTO && relax)
spa_json_init(iter, data, size); spa_json_init_relax(iter, type, data, size);
else if (res <= 0) else if (res <= 0)
return res; return res;
return 1; return 1;

View file

@ -1,4 +1,6 @@
#undef SPA_AUDIO_MAX_CHANNELS
#define SPA_API_IMPL SPA_EXPORT #define SPA_API_IMPL SPA_EXPORT
#include <spa/utils/defs.h> #include <spa/utils/defs.h>
#include <spa/buffer/alloc.h> #include <spa/buffer/alloc.h>
@ -165,9 +167,3 @@
#include <spa/utils/string.h> #include <spa/utils/string.h>
#include <spa/utils/type.h> #include <spa/utils/type.h>
#include <spa/utils/type-info.h> #include <spa/utils/type-info.h>

View file

@ -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); pa_proplist_update(dev->proplist, PA_UPDATE_REPLACE, m->input_proplist);
} }
if (m->split) { if (m->split) {
char pos[2048]; char pos[PA_CHANNELS_MAX*8];
struct spa_strbuf b; struct spa_strbuf b;
int i; 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); pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED);
} else { } else {
uint32_t positions[eld.lpcm_channels]; uint32_t positions[eld.lpcm_channels];
char position[64]; char position[eld.lpcm_channels * 8];
int i = 0, pos = 0; struct spa_strbuf b;
int i = 0;
if (eld.speakers & 0x01) { if (eld.speakers & 0x01) {
positions[i++] = ACP_CHANNEL_FL; 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) { if (eld.speakers & 0x10) {
positions[i++] = ACP_CHANNEL_RC; positions[i++] = ACP_CHANNEL_RC;
} }
while (i < eld.lpcm_channels) while (i < eld.lpcm_channels)
positions[i++] = ACP_CHANNEL_UNKNOWN; positions[i++] = ACP_CHANNEL_UNKNOWN;
for (i = 0, pos = 0; i < eld.lpcm_channels; i++) { spa_strbuf_init(&b, position, sizeof(position));
pos += snprintf(&position[pos], sizeof(position) - pos, "%s,", channel_names[positions[i]]); spa_strbuf_append(&b, "[");
} for (i = 0; i < eld.lpcm_channels; i++)
spa_strbuf_append(&b, "%s%s", i ? "," : "", channel_names[positions[i]]);
/* Overwrite trailing , */ spa_strbuf_append(&b, "]");
position[pos - 1] = 0;
changed |= (old_position == NULL) || (!spa_streq(old_position, position)); changed |= (old_position == NULL) || (!spa_streq(old_position, position));
pa_proplist_sets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED, position); pa_proplist_sets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED, position);

View file

@ -27,7 +27,11 @@
extern "C" { extern "C" {
#endif #endif
#ifdef SPA_AUDIO_MAX_CHANNELS
#define PA_CHANNELS_MAX ((int)SPA_AUDIO_MAX_CHANNELS)
#else
#define PA_CHANNELS_MAX 64 #define PA_CHANNELS_MAX 64
#endif
#define PA_CHANNEL_MAP_SNPRINT_MAX (PA_CHANNELS_MAX * 32) #define PA_CHANNEL_MAP_SNPRINT_MAX (PA_CHANNELS_MAX * 32)
@ -451,7 +455,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) { static inline char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) {
unsigned channel; unsigned channel;
bool first = true;
char *e; char *e;
if (!pa_channel_map_valid(map)) { if (!pa_channel_map_valid(map)) {
pa_snprintf(s, l, "%s", _("(invalid)")); pa_snprintf(s, l, "%s", _("(invalid)"));
@ -460,12 +463,10 @@ static inline char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_m
*(e = s) = 0; *(e = s) = 0;
for (channel = 0; channel < map->channels && l > 1; channel++) { for (channel = 0; channel < map->channels && l > 1; channel++) {
l -= pa_snprintf(e, l, "%s%s", l -= pa_snprintf(e, l, "%s%s",
first ? "" : ",", channel == 0 ? "" : ",",
pa_channel_position_to_string(map->map[channel])); pa_channel_position_to_string(map->map[channel]));
e = strchr(e, 0); e = strchr(e, 0);
first = false;
} }
return s; return s;
} }

View file

@ -156,12 +156,13 @@ static int emit_node(struct impl *this, struct acp_device *dev)
const struct acp_dict_item *it; const struct acp_dict_item *it;
uint32_t n_items, i; uint32_t n_items, i;
char device_name[128], path[210], channels[16], ch[12], routes[16]; 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 positions[MAX_CHANNELS * 12];
char codecs[512]; char codecs[512];
struct spa_device_object_info info; struct spa_device_object_info info;
struct acp_card *card = this->card; struct acp_card *card = this->card;
const char *stream, *card_id, *bus; const char *stream, *card_id, *bus;
struct spa_strbuf b;
info = SPA_DEVICE_OBJECT_INFO_INIT(); info = SPA_DEVICE_OBJECT_INFO_INIT();
info.type = SPA_TYPE_INTERFACE_Node; 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); snprintf(channels, sizeof(channels), "%d", dev->format.channels);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNELS, 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++) { 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])); 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); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_POSITION, positions);
if (dev->n_codecs > 0) { if (dev->n_codecs > 0) {

View file

@ -240,35 +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) static int position_to_string(struct channel_map *map, char *val, size_t len)
{ {
uint32_t i, o = 0; uint32_t i;
int r; char pos[8];
o += snprintf(val, len, "[ "); struct spa_strbuf b;
for (i = 0; i < map->channels; i++) {
r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", spa_strbuf_init(&b, val, len);
spa_debug_type_find_short_name(spa_type_audio_channel, spa_strbuf_append(&b, "[");
map->pos[i])); for (i = 0; i < map->n_pos; i++) {
if (r < 0 || o + r >= len) spa_strbuf_append(&b, "%s%s", i == 0 ? " " : ", ",
return -ENOSPC; spa_type_audio_channel_make_short_name(map->pos[i],
o += r; pos, sizeof(pos), "UNK"));
} }
if (len > o) if (spa_strbuf_append(&b, " ]") < 2)
o += snprintf(val+o, len-o, " ]"); return -ENOSPC;
return 0; return 0;
} }
static int uint32_array_to_string(uint32_t *vals, uint32_t n_vals, char *val, size_t len) static int uint32_array_to_string(uint32_t *vals, uint32_t n_vals, char *val, size_t len)
{ {
uint32_t i, o = 0; uint32_t i;
int r; struct spa_strbuf b;
o += snprintf(val, len, "[ ");
for (i = 0; i < n_vals; i++) { spa_strbuf_init(&b, val, len);
r = snprintf(val+o, len-o, "%s%d", i == 0 ? "" : ", ", vals[i]); spa_strbuf_append(&b, "[");
if (r < 0 || o + r >= len) for (i = 0; i < n_vals; i++)
return -ENOSPC; spa_strbuf_append(&b, "%s%d", i == 0 ? " " : ", ", vals[i]);
o += r; if (spa_strbuf_append(&b, " ]") < 2)
} return -ENOSPC;
if (len > o)
o += snprintf(val+o, len-o, " ]");
return 0; return 0;
} }
@ -775,7 +773,7 @@ static void bind_ctl_event(struct spa_source *source)
snd_ctl_elem_id_alloca(&bound_id); snd_ctl_elem_id_alloca(&bound_id);
snd_ctl_elem_value_alloca(&old_value); 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; bool changed = false;
if (snd_ctl_event_get_type(ev) != SND_CTL_EVENT_ELEM) if (snd_ctl_event_get_type(ev) != SND_CTL_EVENT_ELEM)
@ -1645,7 +1643,7 @@ skip_channels:
} else { } else {
const struct channel_map *map = NULL; const struct channel_map *map = NULL;
spa_pod_builder_int(b, min); spa_pod_builder_int(b, min);
if (state->default_pos.channels == min) { if (state->default_pos.n_pos == min) {
map = &state->default_pos; map = &state->default_pos;
spa_log_debug(state->log, "%p: using provided default", state); spa_log_debug(state->log, "%p: using provided default", state);
} else if (min <= 8) { } else if (min <= 8) {
@ -1655,7 +1653,7 @@ skip_channels:
if (map) { if (map) {
spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_position, 0); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_position, 0);
spa_pod_builder_push_array(b, &f[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_log_debug(state->log, "%p: position %zd %d", state, i, map->pos[i]);
spa_pod_builder_id(b, map->pos[i]); spa_pod_builder_id(b, map->pos[i]);
} }

View file

@ -72,7 +72,7 @@ struct buffer {
#define BW_PERIOD (3 * SPA_NSEC_PER_SEC) #define BW_PERIOD (3 * SPA_NSEC_PER_SEC)
struct channel_map { struct channel_map {
uint32_t channels; uint32_t n_pos;
uint32_t pos[MAX_CHANNELS]; 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) 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) static inline uint32_t spa_alsa_parse_rates(uint32_t *rates, uint32_t max, const char *val, size_t len)

View file

@ -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) { if (spa_format_audio_parse(param, &info) < 0) {
spa_log_error(this->log, "%p: cannot set Format param: " spa_log_error(this->log, "%p: cannot set Format param: "
"parsing the POD failed", this); "parsing the POD failed", this);
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, param);
return -EINVAL; return -EINVAL;
} }
if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) { 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_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) {
spa_log_error(this->log, "%p: cannot set PortConfig param: " spa_log_error(this->log, "%p: cannot set PortConfig param: "
"parsing the POD failed", this); "parsing the POD failed", this);
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, param);
return -EINVAL; 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; struct spa_audio_info info;
spa_zero(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; return res;
}
if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw) { if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw) {
info.info.raw.rate = 0; info.info.raw.rate = 0;

View file

@ -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) uint32_t position, bool is_dsp, bool is_monitor, bool is_control)
{ {
struct port *port = GET_PORT(this, direction, port_id); struct port *port = GET_PORT(this, direction, port_id);
const char *name;
spa_assert(port_id < MAX_PORTS); 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_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT);
port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT);
name = spa_debug_type_find_short_name(spa_type_audio_channel, position); spa_type_audio_channel_make_short_name(position, port->position, sizeof(port->position), "UNK");
snprintf(port->position, sizeof(port->position), "%s", name ? name : "UNK");
port->info = SPA_PORT_INFO_INIT(); port->info = SPA_PORT_INFO_INIT();
port->info.change_mask = port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | port->info.change_mask = port->info_all = SPA_PORT_CHANGE_MASK_FLAGS |
@ -1150,7 +1148,7 @@ struct spa_filter_graph_events graph_events = {
}; };
static int setup_filter_graph(struct impl *this, struct filter_graph *g, 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; int res;
char rate_str[64], in_ports[64]; char rate_str[64], in_ports[64];
@ -1165,9 +1163,8 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g,
snprintf(in_ports, sizeof(in_ports), "%d", channels); snprintf(in_ports, sizeof(in_ports), "%d", channels);
g->n_inputs = channels; g->n_inputs = channels;
if (position) { if (position) {
uint32_t n_pos = SPA_MIN(channels, max_position); memcpy(g->inputs_position, position, sizeof(uint32_t) * channels);
memcpy(g->inputs_position, position, sizeof(uint32_t) * n_pos); memcpy(g->outputs_position, position, sizeof(uint32_t) * channels);
memcpy(g->outputs_position, position, sizeof(uint32_t) * n_pos);
} }
} }
@ -1182,7 +1179,7 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g,
return res; 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) static void free_tmp(struct impl *this)
{ {
@ -1264,7 +1261,7 @@ static int ensure_tmp(struct impl *this)
static int setup_filter_graphs(struct impl *impl, bool force) static int setup_filter_graphs(struct impl *impl, bool force)
{ {
int res; int res;
uint32_t channels, *position, max_position; uint32_t channels, *position;
struct dir *in, *out; struct dir *in, *out;
struct filter_graph *g, *t; struct filter_graph *g, *t;
@ -1273,7 +1270,6 @@ static int setup_filter_graphs(struct impl *impl, bool force)
channels = in->format.info.raw.channels; channels = in->format.info.raw.channels;
position = in->format.info.raw.position; 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); 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) { spa_list_for_each_safe(g, t, &impl->active_graphs, link) {
@ -1281,20 +1277,19 @@ static int setup_filter_graphs(struct impl *impl, bool force)
continue; continue;
if (force) if (force)
g->setup = false; 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; g->removing = true;
spa_log_warn(impl->log, "failed to activate graph %d: %s", g->order, spa_log_warn(impl->log, "failed to activate graph %d: %s", g->order,
spa_strerror(res)); spa_strerror(res));
} else { } else {
channels = g->n_outputs; channels = g->n_outputs;
position = g->outputs_position; position = g->outputs_position;
max_position = SPA_N_ELEMENTS(g->outputs_position);
impl->maxports = SPA_MAX(impl->maxports, channels); impl->maxports = SPA_MAX(impl->maxports, channels);
} }
} }
if ((res = ensure_tmp(impl)) < 0) if ((res = ensure_tmp(impl)) < 0)
return res; return res;
if ((res = setup_channelmix(impl, channels, position, max_position)) < 0) if ((res = setup_channelmix(impl, channels, position)) < 0)
return res; return res;
return 0; return 0;
@ -1543,8 +1538,6 @@ static int get_ramp_samples(struct impl *this, struct volume_ramp_params *vrp)
samples = (vrp->volume_ramp_time * vrp->rate) / 1000; samples = (vrp->volume_ramp_time * vrp->rate) / 1000;
spa_log_info(this->log, "volume ramp samples calculated from time is %d", samples); spa_log_info(this->log, "volume ramp samples calculated from time is %d", samples);
} }
if (!samples)
samples = -1;
return samples; return samples;
} }
@ -1555,12 +1548,10 @@ static int get_ramp_step_samples(struct impl *this, struct volume_ramp_params *v
if (vrp->volume_ramp_step_samples) if (vrp->volume_ramp_step_samples)
samples = vrp->volume_ramp_step_samples; samples = vrp->volume_ramp_step_samples;
else if (vrp->volume_ramp_step_time) { else if (vrp->volume_ramp_step_time) {
/* convert the step time which is in nano seconds to seconds */ /* convert the step time which is in nano seconds to seconds, round up */
samples = (vrp->volume_ramp_step_time/1000) * (vrp->rate/1000); 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); spa_log_debug(this->log, "volume ramp step samples calculated from time is %d", samples);
} }
if (!samples)
samples = -1;
return samples; return samples;
} }
@ -1573,76 +1564,52 @@ static float get_volume_at_scale(struct volume_ramp_params *vrp, float value)
return 0.0; 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) void *buffer, size_t size)
{ {
struct spa_pod_dynamic_builder b; struct spa_pod_dynamic_builder b;
struct spa_pod_frame f[1]; struct spa_pod_frame f[1];
float start = vrp->start, end = vrp->end, volume_accum = start; float start = vrp->start, end = vrp->end;
int ramp_samples = get_ramp_samples(this, vrp); int samples = get_ramp_samples(this, vrp);
int ramp_step_samples = get_ramp_step_samples(this, vrp); int step = get_ramp_step_samples(this, vrp);
float volume_step = ((end - start) / (ramp_samples / ramp_step_samples)); int offs = 0;
uint32_t volume_offs = 0;
if (samples < 0 || step < 0 || (samples > 0 && step == 0))
return NULL;
spa_pod_dynamic_builder_init(&b, buffer, size, 4096); spa_pod_dynamic_builder_init(&b, buffer, size, 4096);
spa_pod_builder_push_sequence(&b.b, &f[0], 0); 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" spa_log_info(this->log, "generating ramp sequence from %f to %f with "
" step value %f at scale %d", start, end, volume_step, vrp->scale); "step %d/%d at scale %d", start, end, step, samples, 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]);
}
static struct spa_pod *generate_ramp_down_seq(struct impl *this, struct volume_ramp_params *vrp, while (1) {
void *buffer, size_t size) float pos = (samples == 0) ? end :
{ SPA_CLAMP(start + (end - start) * offs / samples,
struct spa_pod_dynamic_builder b; SPA_MIN(start, end), SPA_MAX(start, end));
struct spa_pod_frame f[1]; float vas = get_volume_at_scale(vrp, pos);
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;
spa_pod_dynamic_builder_init(&b, buffer, size, 4096); spa_log_trace(this->log, "volume %d accum %f", offs, vas);
spa_pod_builder_control(&b.b, offs, SPA_CONTROL_Properties);
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_pod_builder_add_object(&b.b, spa_pod_builder_add_object(&b.b,
SPA_TYPE_OBJECT_Props, 0, SPA_TYPE_OBJECT_Props, 0,
SPA_PROP_volume, SPA_POD_Float(vas)); SPA_PROP_volume, SPA_POD_Float(vas));
volume_accum -= volume_step; if (offs >= samples)
volume_offs += ramp_step_samples; break;
} while (volume_accum > end);
offs = SPA_MIN(samples, offs + step);
}
return spa_pod_builder_pop(&b.b, &f[0]); return spa_pod_builder_pop(&b.b, &f[0]);
} }
static void generate_volume_ramp(struct impl *this, struct volume_ramp_params *vrp, static void generate_volume_ramp(struct impl *this, struct volume_ramp_params *vrp,
void *buffer, size_t size) void *buffer, size_t size)
{ {
void *sequence = NULL; void *sequence;
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);
sequence = generate_ramp_seq(this, vrp, buffer, size);
if (!sequence) if (!sequence)
spa_log_error(this->log, "unable to generate sequence"); spa_log_error(this->log, "unable to generate sequence");
@ -1899,7 +1866,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; this->dir[SPA_DIRECTION_OUTPUT].n_ports = dir->n_ports + 1;
for (i = 0; i < dir->n_ports; i++) { 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); init_port(this, direction, i, pos, true, false, false);
if (this->monitor && direction == SPA_DIRECTION_INPUT) if (this->monitor && direction == SPA_DIRECTION_INPUT)
init_port(this, SPA_DIRECTION_OUTPUT, i+1, init_port(this, SPA_DIRECTION_OUTPUT, i+1,
@ -2060,16 +2027,16 @@ static int setup_in_convert(struct impl *this)
dst_info.info.raw.channels, dst_info.info.raw.channels,
dst_info.info.raw.rate); dst_info.info.raw.rate);
qsort(dst_info.info.raw.position, SPA_MIN(dst_info.info.raw.channels, qsort(dst_info.info.raw.position, dst_info.info.raw.channels,
SPA_N_ELEMENTS(dst_info.info.raw.position)),
sizeof(uint32_t), int32_cmp); sizeof(uint32_t), int32_cmp);
for (i = 0; i < src_info.info.raw.channels; i++) { for (i = 0; i < src_info.info.raw.channels; i++) {
for (j = 0; j < dst_info.info.raw.channels; j++) { for (j = 0; j < dst_info.info.raw.channels; j++) {
uint32_t pi, pj; uint32_t pi, pj;
char b1[8], b2[8];
pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); pi = src_info.info.raw.position[i];
pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); pj = dst_info.info.raw.position[j];
if (pi != pj) if (pi != pj)
continue; continue;
in->remap[i] = j; in->remap[i] = j;
@ -2077,9 +2044,9 @@ static int setup_in_convert(struct impl *this)
remap = true; remap = true;
spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this,
i, in->remap[i], j, i, in->remap[i], j,
spa_debug_type_find_short_name(spa_type_audio_channel, pi), spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"),
spa_debug_type_find_short_name(spa_type_audio_channel, pj)); 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; break;
} }
} }
@ -2127,7 +2094,7 @@ static int remap_volumes(struct impl *this, const struct spa_audio_info *info)
for (i = 0; i < p->n_channels; i++) { for (i = 0; i < p->n_channels; i++) {
for (j = i; j < target; j++) { 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, spa_log_debug(this->log, "%d %d: %d <-> %d", i, j,
p->channel_map[i], pj); p->channel_map[i], pj);
if (p->channel_map[i] != pj) if (p->channel_map[i] != pj)
@ -2143,7 +2110,7 @@ static int remap_volumes(struct impl *this, const struct spa_audio_info *info)
} }
p->n_channels = target; p->n_channels = target;
for (i = 0; i < p->n_channels; i++) 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) if (target == 0)
return 0; return 0;
@ -2189,17 +2156,18 @@ static void set_volume(struct impl *this)
this->params[IDX_Props].user++; 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; uint32_t i, idx = 0;
char buf[8];
for (i = 0; i < channels; i++) for (i = 0; i < channels; i++)
idx += snprintf(str + idx, len - idx, "%s%s", i == 0 ? "" : " ", idx += snprintf(str + idx, len - idx, "%s%s", i == 0 ? "" : " ",
spa_debug_type_find_short_name(spa_type_audio_channel, spa_type_audio_channel_make_short_name(position[i],
position[i % max_position])); buf, sizeof(buf), "UNK"));
return str; 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 *in = &this->dir[SPA_DIRECTION_INPUT];
struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT];
@ -2212,19 +2180,18 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi
dst_chan = out->format.info.raw.channels; dst_chan = out->format.info.raw.channels;
for (i = 0, src_mask = 0; i < src_chan; i++) { 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); src_mask |= 1ULL << (p < 64 ? p : 0);
} }
for (i = 0, dst_mask = 0; i < dst_chan; i++) { 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); dst_mask |= 1ULL << (p < 64 ? p : 0);
} }
spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), 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), spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str),
dst_chan, out->format.info.raw.position, dst_chan, out->format.info.raw.position), dst_mask);
SPA_N_ELEMENTS(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_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), spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32),
@ -2352,16 +2319,16 @@ static int setup_out_convert(struct impl *this)
dst_info.info.raw.channels, dst_info.info.raw.channels,
dst_info.info.raw.rate); dst_info.info.raw.rate);
qsort(src_info.info.raw.position, SPA_MIN(src_info.info.raw.channels, qsort(src_info.info.raw.position, src_info.info.raw.channels,
SPA_N_ELEMENTS(src_info.info.raw.position)),
sizeof(uint32_t), int32_cmp); sizeof(uint32_t), int32_cmp);
for (i = 0; i < src_info.info.raw.channels; i++) { for (i = 0; i < src_info.info.raw.channels; i++) {
for (j = 0; j < dst_info.info.raw.channels; j++) { for (j = 0; j < dst_info.info.raw.channels; j++) {
uint32_t pi, pj; uint32_t pi, pj;
char b1[8], b2[8];
pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); pi = src_info.info.raw.position[i];
pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); pj = dst_info.info.raw.position[j];
if (pi != pj) if (pi != pj)
continue; continue;
out->remap[i] = j; out->remap[i] = j;
@ -2370,10 +2337,10 @@ static int setup_out_convert(struct impl *this)
spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this,
i, out->remap[i], j, i, out->remap[i], j,
spa_debug_type_find_short_name(spa_type_audio_channel, pi), spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"),
spa_debug_type_find_short_name(spa_type_audio_channel, pj)); 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; break;
} }
} }

View file

@ -551,7 +551,7 @@ int convert_init(struct convert *conv)
const struct dither_info *dinfo; const struct dither_info *dinfo;
const struct noise_info *ninfo; const struct noise_info *ninfo;
const struct clear_info *cinfo; 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 /* we generate int32 bits of random values. With this scale
* factor, we bring this in the [-1.0, 1.0] range */ * 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[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[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[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 + 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) if (conv->data == NULL)
return -errno; return -errno;
conv->noise = SPA_PTR_ALIGN(conv->data, FMT_OPS_MAX_ALIGN, float); conv->noise = SPA_PTR_ALIGN(conv->data, FMT_OPS_MAX_ALIGN, float);
conv->random = SPA_PTROFF(conv->noise, data_size[0], uint32_t); conv->random = SPA_PTROFF(conv->noise, data_size[0], uint32_t);
conv->prev = SPA_PTROFF(conv->random, data_size[1], int32_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++) for (i = 0; i < RANDOM_SIZE; i++)
conv->random[i] = random(); conv->random[i] = random();

View file

@ -236,7 +236,7 @@ struct convert {
uint32_t noise_size; uint32_t noise_size;
const float *ns; const float *ns;
uint32_t n_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 (*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[], void (*process) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[],

View file

@ -125,7 +125,7 @@ sparesampledumpcoeffs_sources = [
sparesampledumpcoeffs = executable( sparesampledumpcoeffs = executable(
'spa-resample-dump-coeffs', 'spa-resample-dump-coeffs',
sparesampledumpcoeffs_sources, sparesampledumpcoeffs_sources,
c_args : [ cc_flags_native, '-DRESAMPLE_DISABLE_PRECOMP' ], c_args : [ '-DRESAMPLE_DISABLE_PRECOMP' ],
dependencies : [ spa_dep, mathlib_native ], dependencies : [ spa_dep, mathlib_native ],
install : false, install : false,
native : true, native : true,

View file

@ -86,11 +86,12 @@ static int position_to_string(struct channel_map *map, char *val, size_t len)
{ {
uint32_t i, o = 0; uint32_t i, o = 0;
int r; int r;
char pos[8];
o += snprintf(val, len, "[ "); o += snprintf(val, len, "[ ");
for (i = 0; i < map->channels; i++) { for (i = 0; i < map->channels; i++) {
r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ",
spa_debug_type_find_short_name(spa_type_audio_channel, spa_type_audio_channel_make_short_name(map->pos[i],
map->pos[i])); pos, sizeof(pos), "UNK"));
if (r < 0 || o + r >= len) if (r < 0 || o + r >= len)
return -ENOSPC; return -ENOSPC;
o += r; o += r;

View file

@ -253,14 +253,8 @@ static const struct surround_encoder_mapping surround_encoders[] = {
static uint32_t bt_channel_from_name(const char *name) static uint32_t bt_channel_from_name(const char *name)
{ {
size_t i; 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++) { for (i = 0; i < SPA_N_ELEMENTS(audio_locations); i++) {
if (position == audio_locations[i].position) if (position == audio_locations[i].position)
return audio_locations[i].mask; return audio_locations[i].mask;
@ -497,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, 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, 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 channels = conf->channels;
const uint32_t location = OPUS_05_GET_LOCATION(*conf); const uint32_t location = OPUS_05_GET_LOCATION(*conf);
@ -545,12 +539,11 @@ static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direc
if (location & loc.mask) { if (location & loc.mask) {
uint32_t idx = permutation ? permutation[j] : j; uint32_t idx = permutation ? permutation[j] : j;
if (idx < max_positions) positions[idx] = loc.position;
positions[idx] = loc.position;
j++; 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; positions[j] = i;
} }
@ -785,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; 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; return -EINVAL;
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id);
@ -838,9 +831,9 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags
info->info.raw.channels = dir1->channels; info->info.raw.channels = dir1->channels;
if (get_mapping(codec, dir1, surround_encoder, NULL, NULL, NULL, 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; 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 -EINVAL;
return 0; return 0;
@ -931,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) if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0)
goto error; goto error;
if ((res = get_mapping(codec, dir, surround_encoder, &this->streams, &this->coupled_streams, if ((res = get_mapping(codec, dir, surround_encoder, &this->streams, &this->coupled_streams,
&enc_mapping, NULL, 0)) < 0) &enc_mapping, NULL)) < 0)
goto error; goto error;
if (config_info.info.raw.channels != info->info.raw.channels) { if (config_info.info.raw.channels != info->info.raw.channels) {
res = -EINVAL; res = -EINVAL;

View file

@ -5038,9 +5038,13 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn,
spa_log_error(monitor->log, "invalid transport configuration"); spa_log_error(monitor->log, "invalid transport configuration");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 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; transport->n_channels = info.info.raw.channels;
spa_format_audio_raw_copy_positions(&info.info.raw, memcpy(transport->channels, info.info.raw.position,
transport->channels, SPA_N_ELEMENTS(transport->channels)); transport->n_channels * sizeof(uint32_t));
} else { } else {
transport->n_channels = 2; transport->n_channels = 2;
transport->channels[0] = SPA_AUDIO_CHANNEL_FL; transport->channels[0] = SPA_AUDIO_CHANNEL_FL;

View file

@ -452,8 +452,7 @@ static int node_offload_set_active(struct node *node, bool active)
return res; return res;
} }
static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels, static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels)
uint32_t max_channels)
{ {
const struct media_codec *codec; const struct media_codec *codec;
struct spa_audio_info info = { 0 }; struct spa_audio_info info = { 0 };
@ -475,7 +474,10 @@ static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t
return; 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) static const char *get_channel_name(uint32_t channel)
@ -686,7 +688,7 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t,
this->nodes[id].active = true; this->nodes[id].active = true;
this->nodes[id].offload_acquired = false; this->nodes[id].offload_acquired = false;
this->nodes[id].a2dp_duplex = a2dp_duplex; 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) if (this->nodes[id].transport)
spa_hook_remove(&this->nodes[id].transport_listener); spa_hook_remove(&this->nodes[id].transport_listener);
this->nodes[id].transport = t; this->nodes[id].transport = t;

View file

@ -232,16 +232,18 @@ struct impl {
float *discard_data; 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; uint32_t i;
struct spa_strbuf buf; struct spa_strbuf buf;
char pos[8];
spa_strbuf_init(&buf, buffer, max_size); spa_strbuf_init(&buf, buffer, max_size);
spa_strbuf_append(&buf, "["); 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_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, "]"); spa_strbuf_append(&buf, "]");
} }

View file

@ -231,6 +231,7 @@ impl_init(const struct spa_handle_factory *factory,
handle->clear = impl_clear, this = (struct impl *) handle; handle->clear = impl_clear, this = (struct impl *) handle;
this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
v4l2_log_topic_init(this->log);
spa_hook_list_init(&this->hooks); spa_hook_list_init(&this->hooks);

View file

@ -775,6 +775,8 @@ impl_init(const struct spa_handle_factory *factory,
#endif #endif
this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); 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); this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop);
if (this->main_loop == NULL) { if (this->main_loop == NULL) {

View file

@ -326,9 +326,9 @@ struct stream {
unsigned int have_latency:1; 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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"),
SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)),
@ -883,8 +883,8 @@ static int create_stream(struct stream_info *info)
s->remap[i] = i; s->remap[i] = i;
for (j = 0; j < tmp_info.channels; j++) { for (j = 0; j < tmp_info.channels; j++) {
uint32_t pj, pi; uint32_t pj, pi;
pj = spa_format_audio_raw_get_position(&tmp_info, j); pj = tmp_info.position[j];
pi = spa_format_audio_raw_get_position(&remap_info, i); pi = remap_info.position[i];
if (pj == pi) { if (pj == pi) {
s->remap[i] = j; s->remap[i] = j;
break; break;
@ -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.prefill");
copy_props(props, impl->combine_props, "resample.disable"); 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_LOOP_NAME);
copy_props(props, impl->stream_props, PW_KEY_NODE_GROUP); copy_props(props, impl->stream_props, PW_KEY_NODE_GROUP);

View file

@ -229,8 +229,10 @@ struct impl {
struct spa_audio_aec *aec; struct spa_audio_aec *aec;
uint32_t aec_blocksize; uint32_t aec_blocksize;
unsigned int capture_ready:1; struct spa_io_position *capture_position;
unsigned int sink_ready:1; struct spa_io_position *sink_position;
uint32_t capture_cycle;
uint32_t sink_cycle;
unsigned int do_disconnect:1; unsigned int do_disconnect:1;
@ -307,13 +309,24 @@ static void process(struct impl *impl)
const float *play_delayed[impl->play_info.channels]; const float *play_delayed[impl->play_info.channels];
float *out[impl->out_info.channels]; float *out[impl->out_info.channels];
struct spa_data *dd; struct spa_data *dd;
uint32_t i, size; uint32_t i;
uint32_t rindex, pindex, oindex, pdindex, avail; uint32_t rindex, pindex, oindex, pdindex, size;
int32_t avail, pavail, pdavail;
size = impl->aec_blocksize; size = impl->aec_blocksize;
/* First read a block from the playback and capture ring buffers */ /* First read a block from the capture ring buffer */
spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex);
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++) { for (i = 0; i < impl->rec_info.channels; i++) {
/* captured samples, with echo from sink */ /* captured samples, with echo from sink */
@ -331,19 +344,34 @@ static void process(struct impl *impl)
out[i] = &out_buf[i][0]; out[i] = &out_buf[i][0];
} }
spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex);
spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex);
if (impl->playback != NULL && (pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) { if (impl->playback != NULL && (pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) {
pw_log_debug("out of playback buffers: %m"); pw_log_debug("out of playback buffers: %m");
/* playback stream may not yet be in streaming state, drop play /* playback stream may not yet be in streaming state, drop play
* data to avoid introducing additional playback latency */ * data to avoid introducing additional playback latency */
spa_ringbuffer_read_update(&impl->play_ring, pindex + size); spa_ringbuffer_read_update(&impl->play_ring, pindex + pavail);
spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + pdavail);
goto done; goto done;
} }
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);
}
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++) { for (i = 0; i < impl->play_info.channels; i++) {
/* echo from sink */ /* echo from sink */
play[i] = &play_buf[i][0]; play[i] = &play_buf[i][0];
@ -431,7 +459,7 @@ static void process(struct impl *impl)
* available on the source */ * available on the source */
avail = spa_ringbuffer_get_read_index(&impl->out_ring, &oindex); 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) { if ((cout = pw_stream_dequeue_buffer(impl->source)) != NULL) {
for (i = 0; i < impl->out_info.channels; i++) { for (i = 0; i < impl->out_info.channels; i++) {
dd = &cout->buffer->datas[i]; dd = &cout->buffer->datas[i];
@ -454,8 +482,8 @@ static void process(struct impl *impl)
} }
done: done:
impl->sink_ready = false; impl->capture_cycle = 0;
impl->capture_ready = false; impl->sink_cycle = 0;
} }
static void reset_buffers(struct impl *impl) static void reset_buffers(struct impl *impl)
@ -479,8 +507,8 @@ static void reset_buffers(struct impl *impl)
spa_ringbuffer_get_read_index(&impl->play_ring, &index); spa_ringbuffer_get_read_index(&impl->play_ring, &index);
spa_ringbuffer_read_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); spa_ringbuffer_read_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay)));
impl->sink_ready = false; impl->capture_cycle = 0;
impl->capture_ready = false; impl->sink_cycle = 0;
} }
static void capture_destroy(void *d) static void capture_destroy(void *d)
@ -546,8 +574,11 @@ static void capture_process(void *data)
spa_ringbuffer_write_update(&impl->rec_ring, index + size); spa_ringbuffer_write_update(&impl->rec_ring, index + size);
if (avail + size >= impl->aec_blocksize) { if (avail + size >= impl->aec_blocksize) {
impl->capture_ready = true; if (impl->capture_position)
if (impl->sink_ready) impl->capture_cycle = impl->capture_position->clock.cycle;
else
pw_log_warn("no capture position");
if (impl->capture_cycle == impl->sink_cycle)
process(impl); process(impl);
} }
@ -740,12 +771,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 = { static const struct pw_stream_events capture_events = {
PW_VERSION_STREAM_EVENTS, PW_VERSION_STREAM_EVENTS,
.destroy = capture_destroy, .destroy = capture_destroy,
.state_changed = capture_state_changed, .state_changed = capture_state_changed,
.process = capture_process, .process = capture_process,
.param_changed = input_param_changed .param_changed = input_param_changed,
.io_changed = capture_io_changed
}; };
static void source_destroy(void *d) static void source_destroy(void *d)
@ -930,10 +975,15 @@ static void sink_process(void *data)
SPA_PTROFF(d->data, offs, void), size); SPA_PTROFF(d->data, offs, void), size);
} }
spa_ringbuffer_write_update(&impl->play_ring, index + 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) { if (avail + size >= impl->aec_blocksize) {
impl->sink_ready = true; if (impl->sink_position)
if (impl->capture_ready) impl->sink_cycle = impl->sink_position->clock.cycle;
else
pw_log_warn("no sink position");
if (impl->capture_cycle == impl->sink_cycle)
process(impl); process(impl);
} }
@ -955,12 +1005,27 @@ static const struct pw_stream_events playback_events = {
.state_changed = playback_state_changed, .state_changed = playback_state_changed,
.param_changed = output_param_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 = { static const struct pw_stream_events sink_events = {
PW_VERSION_STREAM_EVENTS, PW_VERSION_STREAM_EVENTS,
.destroy = sink_destroy, .destroy = sink_destroy,
.process = sink_process, .process = sink_process,
.state_changed = sink_state_changed, .state_changed = sink_state_changed,
.param_changed = output_param_changed .param_changed = output_param_changed,
.io_changed = sink_io_changed
}; };
#define MAX_PARAMS 512u #define MAX_PARAMS 512u
@ -1203,9 +1268,9 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy, .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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"),
SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)),
@ -1291,7 +1356,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
if (pw_properties_get(props, "resample.prefill") == NULL) if (pw_properties_get(props, "resample.prefill") == NULL)
pw_properties_set(props, "resample.prefill", "true"); 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->capture_info = info;
impl->source_info = info; impl->source_info = info;

View file

@ -467,9 +467,9 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy, .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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"),
SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), 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) if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL)
pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str); pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str);
parse_audio_info(impl->capture_props, &impl->capture_info); if ((res = parse_audio_info(impl->capture_props, &impl->capture_info)) < 0 ||
parse_audio_info(impl->playback_props, &impl->playback_info); (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 (!impl->capture_info.rate && !impl->playback_info.rate) {
if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) if (pw_properties_get(impl->playback_props, "resample.disable") == NULL)

View file

@ -273,9 +273,9 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy, .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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT),
SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), 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_NODE_VIRTUAL);
copy_props(impl, props, PW_KEY_MEDIA_CLASS); 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); impl->frame_size = calc_frame_size(&impl->info);
if (impl->frame_size == 0) { if (impl->frame_size == 0) {

View file

@ -279,9 +279,9 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy, .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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT),
SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), 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_NODE_VIRTUAL);
copy_props(impl, props, PW_KEY_MEDIA_CLASS); 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); impl->frame_size = calc_frame_size(&impl->info);
if (impl->frame_size == 0) { if (impl->frame_size == 0) {

View file

@ -761,7 +761,7 @@ static int make_stream_ports(struct stream *s)
struct port *port = s->ports[i]; struct port *port = s->ports[i];
char channel[32]; 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) { switch (port->stream_type) {
case ffado_stream_type_audio: case ffado_stream_type_audio:
@ -1229,7 +1229,7 @@ static int probe_ffado_device(struct impl *impl)
} }
if (impl->source.info.channels != n_channels) { if (impl->source.info.channels != n_channels) {
uint32_t n_pos = SPA_MIN(n_channels, SPA_N_ELEMENTS(impl->source.info.position)); 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++) for (i = 0; i < n_pos; i++)
impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + 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) { if (impl->sink.info.channels != n_channels) {
uint32_t n_pos = SPA_MIN(n_channels, SPA_N_ELEMENTS(impl->sink.info.position)); 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++) for (i = 0; i < n_pos; i++)
impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
} }
@ -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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"),
SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), 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_VIRTUAL);
copy_props(impl, props, PW_KEY_NODE_PAUSE_ON_IDLE); copy_props(impl, props, PW_KEY_NODE_PAUSE_ON_IDLE);
parse_audio_info(impl->source.props, &impl->source.info); if ((res = parse_audio_info(impl->source.props, &impl->source.info)) < 0 ||
parse_audio_info(impl->sink.props, &impl->sink.info); (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); impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core);
if (impl->core == NULL) { if (impl->core == NULL) {

View file

@ -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) && if (SPA_FLAG_IS_SET(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED) &&
!SPA_FLAG_IS_SET(src->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 (uint32_t i = 0; i < src->channels; i++)
for (i = 0; i < n_pos; i++)
dst->position[i] = src->position[i]; dst->position[i] = src->position[i];
SPA_FLAG_CLEAR(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED); SPA_FLAG_CLEAR(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
} }
@ -1833,9 +1832,9 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy, .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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")),
&props->dict, &props->dict,
@ -1928,8 +1927,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, PW_KEY_MEDIA_NAME);
copy_props(impl, props, "resample.prefill"); copy_props(impl, props, "resample.prefill");
parse_audio_info(impl->capture_props, &impl->capture_info); if ((res = parse_audio_info(impl->capture_props, &impl->capture_info)) < 0 ||
parse_audio_info(impl->playback_props, &impl->playback_info); (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 (!impl->capture_info.rate && !impl->playback_info.rate) {
if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) if (pw_properties_get(impl->playback_props, "resample.disable") == NULL)

View file

@ -514,6 +514,7 @@ static void make_stream_ports(struct stream *s)
for (i = 0; i < s->n_ports; i++) { for (i = 0; i < s->n_ports; i++) {
struct port *port = s->ports[i]; struct port *port = s->ports[i];
char *link_port = NULL; char *link_port = NULL;
char pos[8];
if (port != NULL) { if (port != NULL) {
s->ports[i] = NULL; s->ports[i] = NULL;
@ -523,8 +524,8 @@ static void make_stream_ports(struct stream *s)
} }
if (i < s->info.channels) { if (i < s->info.channels) {
str = spa_debug_type_find_short_name(spa_type_audio_channel, str = spa_type_audio_channel_make_short_name(
spa_format_audio_raw_get_position(&s->info, i)); s->info.position[i], pos, sizeof(pos), NULL);
if (str) if (str)
snprintf(name, sizeof(name), "%s_%s", prefix, str); snprintf(name, sizeof(name), "%s_%s", prefix, str);
else else
@ -1051,9 +1052,9 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy, .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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"),
SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)),
@ -1176,8 +1177,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-audio");
copy_props(impl, props, "jack.connect-midi"); copy_props(impl, props, "jack.connect-midi");
parse_audio_info(impl->source.props, &impl->source.info); if ((res = parse_audio_info(impl->source.props, &impl->source.info)) < 0 ||
parse_audio_info(impl->sink.props, &impl->sink.info); (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, impl->source.n_midi = pw_properties_get_uint32(impl->source.props,
"midi.ports", DEFAULT_MIDI_PORTS); "midi.ports", DEFAULT_MIDI_PORTS);

View file

@ -546,7 +546,7 @@ static void param_format_changed(struct impl *impl, const struct spa_pod *param,
(impl->info.channels != 0 && (impl->info.channels != 0 &&
(impl->info.channels != info.channels || (impl->info.channels != info.channels ||
memcmp(impl->info.position, info.position, 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]; uint8_t buffer[1024];
struct spa_pod_builder b; struct spa_pod_builder b;
const struct spa_pod *params[1]; const struct spa_pod *params[1];
@ -838,9 +838,9 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy, .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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")),
&props->dict, &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) if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL)
pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str); pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str);
parse_audio_info(props, &impl->info); if ((res = parse_audio_info(props, &impl->info)) < 0 ||
parse_audio_info(impl->capture_props, &impl->capture_info); (res = parse_audio_info(impl->capture_props, &impl->capture_info)) < 0 ||
parse_audio_info(impl->playback_props, &impl->playback_info); (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 (!impl->capture_info.rate && !impl->playback_info.rate) {
if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) if (pw_properties_get(impl->playback_props, "resample.disable") == NULL)

View file

@ -441,11 +441,11 @@ static void make_stream_ports(struct stream *s)
} }
if (i < s->info.channels) { if (i < s->info.channels) {
str = spa_debug_type_find_short_name(spa_type_audio_channel, str = spa_type_audio_channel_make_short_name(
spa_format_audio_raw_get_position(&s->info, i)); s->info.position[i], name, sizeof(name), "UNK");
props = pw_properties_new( props = pw_properties_new(
PW_KEY_FORMAT_DSP, "32 bit float mono audio", 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", PW_KEY_PORT_PHYSICAL, "true",
NULL); NULL);
@ -865,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) { if ((uint32_t)peer->params.send_audio_channels != impl->sink.info.channels) {
impl->sink.info.channels = peer->params.send_audio_channels; impl->sink.info.channels = peer->params.send_audio_channels;
for (i = 0; i < impl->sink.info.channels; i++) for (i = 0; i < impl->sink.info.channels; i++)
spa_format_audio_raw_set_position(&impl->sink.info, i, impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
SPA_AUDIO_CHANNEL_AUX0 + i);
} }
impl->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; impl->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels;
if (impl->source.n_ports > MAX_PORTS) { if (impl->source.n_ports > MAX_PORTS) {
@ -877,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) { if ((uint32_t)peer->params.recv_audio_channels != impl->source.info.channels) {
impl->source.info.channels = peer->params.recv_audio_channels; impl->source.info.channels = peer->params.recv_audio_channels;
for (i = 0; i < impl->source.info.channels; i++) for (i = 0; i < impl->source.info.channels; i++)
spa_format_audio_raw_set_position(&impl->source.info, i, impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
SPA_AUDIO_CHANNEL_AUX0 + i);
} }
impl->samplerate = peer->params.sample_rate; impl->samplerate = peer->params.sample_rate;
impl->period_size = peer->params.period_size; impl->period_size = peer->params.period_size;
@ -1220,9 +1218,9 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy, .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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")),
&props->dict, &props->dict,
@ -1338,8 +1336,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
copy_props(impl, props, "midi.ports"); copy_props(impl, props, "midi.ports");
copy_props(impl, props, "audio.ports"); copy_props(impl, props, "audio.ports");
parse_audio_info(impl->source.props, &impl->source.info); if ((res = parse_audio_info(impl->source.props, &impl->source.info)) < 0 ||
parse_audio_info(impl->sink.props, &impl->sink.info); (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, impl->source.wanted_n_midi = pw_properties_get_int32(impl->source.props,
"midi.ports", DEFAULT_MIDI_PORTS); "midi.ports", DEFAULT_MIDI_PORTS);

View file

@ -181,7 +181,7 @@ static const struct spa_dict_item module_props[] = {
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, { 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 { struct port {
enum spa_direction direction; enum spa_direction direction;
@ -601,12 +601,11 @@ static void make_stream_ports(struct stream *s)
} }
if (i < s->info.channels) { if (i < s->info.channels) {
str = spa_debug_type_find_short_name(spa_type_audio_channel, str = spa_type_audio_channel_make_short_name(
spa_format_audio_raw_get_position(&s->info, i)); s->info.position[i], name, sizeof(name), "UNK");
props = pw_properties_new( props = pw_properties_new(
PW_KEY_FORMAT_DSP, "32 bit float mono audio", 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", PW_KEY_PORT_PHYSICAL, "true",
NULL); NULL);
@ -969,8 +968,11 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param
follower->sink.direction = PW_DIRECTION_INPUT; follower->sink.direction = PW_DIRECTION_INPUT;
follower->sink.props = pw_properties_copy(impl->sink_props); follower->sink.props = pw_properties_copy(impl->sink_props);
parse_audio_info(follower->source.props, &follower->source.info); if ((res = parse_audio_info(follower->source.props, &follower->source.info)) < 0 ||
parse_audio_info(follower->sink.props, &follower->sink.info); (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, follower->source.n_audio = pw_properties_get_uint32(follower->source.props,
"audio.ports", follower->source.info.channels ? "audio.ports", follower->source.info.channels ?
@ -1028,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) { if ((uint32_t)peer->params.recv_audio_channels != follower->source.info.channels) {
follower->source.info.channels = peer->params.recv_audio_channels; follower->source.info.channels = peer->params.recv_audio_channels;
for (i = 0; i < follower->source.info.channels; i++) for (i = 0; i < follower->source.info.channels; i++)
spa_format_audio_raw_set_position(&follower->source.info, i, follower->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
SPA_AUDIO_CHANNEL_AUX0 + i);
} }
follower->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; follower->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels;
follower->sink.info.rate = peer->params.sample_rate; follower->sink.info.rate = peer->params.sample_rate;
if ((uint32_t)peer->params.send_audio_channels != follower->sink.info.channels) { if ((uint32_t)peer->params.send_audio_channels != follower->sink.info.channels) {
follower->sink.info.channels = peer->params.send_audio_channels; follower->sink.info.channels = peer->params.send_audio_channels;
for (i = 0; i < follower->sink.info.channels; i++) for (i = 0; i < follower->sink.info.channels; i++)
spa_format_audio_raw_set_position(&follower->sink.info, i, follower->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
SPA_AUDIO_CHANNEL_AUX0 + i);
} }
if (follower->source.n_ports > MAX_PORTS || follower->sink.n_ports > MAX_PORTS) { if (follower->source.n_ports > MAX_PORTS || follower->sink.n_ports > MAX_PORTS) {
@ -1292,9 +1292,9 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy, .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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")),
&props->dict, &props->dict,

View file

@ -745,9 +745,9 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy, .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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT),
SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), 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, PW_KEY_TARGET_OBJECT);
copy_props(impl, props, "pipe.filename"); 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); impl->frame_size = calc_frame_size(&impl->info);
if (impl->frame_size == 0) { if (impl->frame_size == 0) {

View file

@ -298,9 +298,9 @@ uint32_t channel_pa2id(enum channel_position channel)
return audio_channels[channel].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) uint32_t channel_name2id(const char *name)
@ -585,7 +585,7 @@ int format_parse_param(const struct spa_pod *param, bool collect,
if (info.info.raw.channels) { if (info.info.raw.channels) {
map->channels = SPA_MIN(info.info.raw.channels, CHANNELS_MAX); map->channels = SPA_MIN(info.info.raw.channels, CHANNELS_MAX);
for (i = 0; i < map->channels; i++) 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; return 0;

View file

@ -190,7 +190,7 @@ void sample_spec_fix(struct sample_spec *ss, struct channel_map *map,
struct spa_dict *props); struct spa_dict *props);
uint32_t channel_pa2id(enum channel_position channel); 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); uint32_t channel_name2id(const char *name);
enum channel_position channel_id2pa(uint32_t id, uint32_t *aux); enum channel_position channel_id2pa(uint32_t id, uint32_t *aux);
const char *channel_id2paname(uint32_t id, uint32_t *aux); const char *channel_id2paname(uint32_t id, uint32_t *aux);

View file

@ -761,11 +761,13 @@ int message_dump(enum spa_log_level level, const char *prefix, struct message *m
case TAG_CHANNEL_MAP: case TAG_CHANNEL_MAP:
{ {
struct channel_map map; struct channel_map map;
char pos[8];
if ((res = read_channel_map(m, &map)) < 0) if ((res = read_channel_map(m, &map)) < 0)
return res; return res;
pw_log(level, "%s %u: channelmap: channels:%u", prefix, o, map.channels); pw_log(level, "%s %u: channelmap: channels:%u", prefix, o, map.channels);
for (i = 0; i < map.channels; i++) 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; break;
} }
case TAG_CVOLUME: case TAG_CVOLUME:

View file

@ -244,7 +244,7 @@ int module_args_to_audioinfo_keys(struct impl *impl, struct pw_properties *props
} else { } else {
/* FIXME add more mappings */ /* FIXME add more mappings */
for (i = 0; i < info->channels; i++) 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) if (info->position[0] == SPA_AUDIO_CHANNEL_UNKNOWN)
info->flags |= SPA_AUDIO_FLAG_UNPOSITIONED; info->flags |= SPA_AUDIO_FLAG_UNPOSITIONED;
@ -283,14 +283,14 @@ void audioinfo_to_properties(struct spa_audio_info_raw *info, struct pw_properti
if (info->rate) if (info->rate)
pw_properties_setf(props, SPA_KEY_AUDIO_RATE, "%u", info->rate); pw_properties_setf(props, SPA_KEY_AUDIO_RATE, "%u", info->rate);
if (info->channels) { if (info->channels) {
char *s, *p; char *s, *p, pos[8];
pw_properties_setf(props, SPA_KEY_AUDIO_CHANNELS, "%u", info->channels); pw_properties_setf(props, SPA_KEY_AUDIO_CHANNELS, "%u", info->channels);
p = s = alloca(info->channels * 8); p = s = alloca(info->channels * 8);
for (i = 0; i < info->channels; i++) for (i = 0; i < info->channels; i++)
p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ", ", p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ", ",
channel_id2name(spa_format_audio_raw_get_position(info, i))); channel_id2name(info->position[i], pos, sizeof(pos)));
pw_properties_setf(props, SPA_KEY_AUDIO_POSITION, "[ %s ]", s); pw_properties_setf(props, SPA_KEY_AUDIO_POSITION, "[ %s ]", s);
} }
} }

View file

@ -295,9 +295,11 @@ static int do_extension_stream_restore_write(struct module *module, struct clien
fprintf(f, " ]"); fprintf(f, " ]");
} }
if (map.channels > 0) { if (map.channels > 0) {
char pos[8];
fprintf(f, ", \"channels\": ["); fprintf(f, ", \"channels\": [");
for (i = 0; i < map.channels; i++) 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, " ]"); fprintf(f, " ]");
} }
if (device_name != NULL && device_name[0] && if (device_name != NULL && device_name[0] &&

View file

@ -815,13 +815,14 @@ static int calc_frame_size(struct spa_audio_info_raw *info)
case SPA_AUDIO_FORMAT_F64_OE: case SPA_AUDIO_FORMAT_F64_OE:
return res * 8; return res * 8;
default: default:
return 0; return -ENOTSUP;
} }
} }
static int 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, int res;
if ((res = spa_audio_info_raw_init_dict_keys(info,
&SPA_DICT_ITEMS( &SPA_DICT_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT),
SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), 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_FORMAT,
SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_RATE,
SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_CHANNELS,
SPA_KEY_AUDIO_POSITION, NULL); SPA_KEY_AUDIO_POSITION, NULL)) < 0)
return res;
return calc_frame_size(info); return calc_frame_size(info);
} }
@ -851,6 +853,7 @@ static int parse_params(struct impl *impl)
const char *str; const char *str;
struct spa_json it[1]; struct spa_json it[1];
char value[512]; char value[512];
int res;
pw_properties_fetch_bool(impl->props, "capture", &impl->capture); pw_properties_fetch_bool(impl->props, "capture", &impl->capture);
pw_properties_fetch_bool(impl->props, "playback", &impl->playback); 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_VIRTUAL);
copy_props(impl, PW_KEY_NODE_NETWORK); copy_props(impl, PW_KEY_NODE_NETWORK);
impl->capture_frame_size = parse_audio_info(impl->capture_props, &impl->capture_info); if ((res = parse_audio_info(impl->capture_props, &impl->capture_info)) <= 0) {
if (impl->capture_frame_size == 0) {
pw_log_error("unsupported capture audio format:%d channels:%d", pw_log_error("unsupported capture audio format:%d channels:%d",
impl->capture_info.format, impl->capture_info.channels); impl->capture_info.format, impl->capture_info.channels);
return -EINVAL; return -EINVAL;
} }
impl->capture_frame_size = res;
impl->playback_frame_size = parse_audio_info(impl->playback_props, &impl->playback_info); if ((res = parse_audio_info(impl->playback_props, &impl->playback_info)) <= 0) {
if (impl->playback_frame_size == 0) {
pw_log_error("unsupported playback audio format:%d channels:%d", pw_log_error("unsupported playback audio format:%d channels:%d",
impl->playback_info.format, impl->playback_info.channels); impl->playback_info.format, impl->playback_info.channels);
return -EINVAL; return -EINVAL;
} }
impl->playback_frame_size = res;
if (impl->capture_info.rate != 0 && if (impl->capture_info.rate != 0 &&
pw_properties_get(impl->capture_props, PW_KEY_NODE_RATE) == NULL) pw_properties_get(impl->capture_props, PW_KEY_NODE_RATE) == NULL)
pw_properties_setf(impl->capture_props, PW_KEY_NODE_RATE, pw_properties_setf(impl->capture_props, PW_KEY_NODE_RATE,

View file

@ -755,7 +755,7 @@ static int create_pulse_stream(struct impl *impl)
map.channels = impl->info.channels; map.channels = impl->info.channels;
for (i = 0; i < map.channels; i++) for (i = 0; i < map.channels; i++)
map.map[i] = (pa_channel_position_t)channel_id2pa( 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"), snprintf(stream_name, sizeof(stream_name), _("Tunnel for %s@%s"),
pw_get_user_name(), pw_get_host_name()); pw_get_user_name(), pw_get_host_name());
@ -1048,9 +1048,9 @@ static const struct pw_impl_module_events module_events = {
.destroy = module_destroy, .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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT),
SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), 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_NODE_NETWORK);
copy_props(impl, props, PW_KEY_MEDIA_CLASS); 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); impl->frame_size = calc_frame_size(&impl->info);
if (impl->frame_size == 0) { if (impl->frame_size == 0) {

View file

@ -569,9 +569,9 @@ static const struct format_info *find_audio_format_info(const struct spa_audio_i
return NULL; 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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT),
SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), 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) { switch (impl->info.media_subtype) {
case SPA_MEDIA_SUBTYPE_raw: 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->stream_info = impl->info;
impl->format_info = find_audio_format_info(&impl->info); impl->format_info = find_audio_format_info(&impl->info);
if (impl->format_info == NULL) { if (impl->format_info == NULL) {
@ -704,7 +707,10 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core,
case SPA_MEDIA_SUBTYPE_opus: case SPA_MEDIA_SUBTYPE_opus:
impl->stream_info.media_type = SPA_MEDIA_TYPE_audio; impl->stream_info.media_type = SPA_MEDIA_TYPE_audio;
impl->stream_info.media_subtype = SPA_MEDIA_SUBTYPE_raw; 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->stream_info.info.raw.format = SPA_AUDIO_FORMAT_F32;
impl->info.info.opus.rate = impl->stream_info.info.raw.rate; impl->info.info.opus.rate = impl->stream_info.info.raw.rate;
impl->info.info.opus.channels = impl->stream_info.info.raw.channels; impl->info.info.opus.channels = impl->stream_info.info.raw.channels;

View file

@ -503,9 +503,11 @@ static int add_snapcast_stream(struct impl *impl, struct tunnel *t,
return -ENOENT; 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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT),
SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), 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_FORMAT,
SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_RATE,
SPA_KEY_AUDIO_CHANNELS, 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, pw_properties_set(props, PW_KEY_AUDIO_FORMAT,
spa_type_audio_format_to_short_name(info->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_RATE, "%d", info->rate);
pw_properties_setf(props, PW_KEY_AUDIO_CHANNELS, "%d", info->channels); pw_properties_setf(props, PW_KEY_AUDIO_CHANNELS, "%d", info->channels);
return res;
} }
static int create_stream(struct impl *impl, struct pw_properties *props, 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) if ((str = pw_properties_get(props, "capture.props")) == NULL)
pw_properties_set(props, "capture.props", "{ media.class = Audio/Sink }"); 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) { if ((f = open_memstream(&args, &size)) == NULL) {
res = -errno; res = -errno;

View file

@ -185,9 +185,9 @@ static const struct format_info *find_audio_format_info(const struct spa_audio_i
return NULL; 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_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT),
SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)),
@ -215,16 +215,16 @@ static const struct spa_audio_layout_info layouts[] = {
{ SPA_AUDIO_LAYOUT_7_1 }, { 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) { SPA_FOR_EACH_ELEMENT_VAR(layouts, l) {
if (l->n_channels == channels) { 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]; position[i] = l->position[i];
return; 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; position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
} }
@ -274,11 +274,14 @@ struct vban_stream *vban_stream_new(struct pw_core *core,
switch (impl->info.media_subtype) { switch (impl->info.media_subtype) {
case SPA_MEDIA_SUBTYPE_raw: case SPA_MEDIA_SUBTYPE_raw:
parse_audio_info(props, &impl->info.info.raw); if ((res = parse_audio_info(props, &impl->info.info.raw)) < 0) {
if (SPA_FLAG_IS_SET(impl->info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED)) pw_log_error("can't parse format: %s", spa_strerror(res));
default_layout(impl->info.info.raw.channels, goto out;
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->stream_info = impl->info;
impl->format_info = find_audio_format_info(&impl->info); impl->format_info = find_audio_format_info(&impl->info);
if (impl->format_info == NULL) { if (impl->format_info == NULL) {

View file

@ -188,7 +188,7 @@ static void pw_properties_from_avahi_string(const char *key, const char *value,
else if (spa_streq(key, "channel_map")) { else if (spa_streq(key, "channel_map")) {
struct channel_map channel_map; struct channel_map channel_map;
uint32_t i, pos[CHANNELS_MAX]; uint32_t i, pos[CHANNELS_MAX];
char *p, *s; char *p, *s, buf[8];
spa_zero(channel_map); spa_zero(channel_map);
channel_map_parse(value, &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, "["); p += spa_scnprintf(p, 2, "[");
for (i = 0; i < channel_map.channels; i++) for (i = 0; i < channel_map.channels; i++)
p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ",", 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, "]"); p += spa_scnprintf(p, 2, "]");
pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s); pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s);
} }

View file

@ -252,7 +252,8 @@ static int update_string(struct pw_properties *props, const char *str, size_t si
continue; continue;
} }
/* item changed or added, apply changes later */ /* 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; it[0].state = SPA_JSON_ERROR_FLAG;
break; break;
} }

View file

@ -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) static void expose_settings(struct pw_context *context, struct pw_impl_metadata *metadata)
{ {
struct settings *s = &context->settings; struct settings *s = &context->settings;
uint32_t i, o; uint32_t i;
char rates[MAX_RATES*16] = ""; char rates[MAX_RATES*16];
struct spa_strbuf b;
pw_impl_metadata_set_propertyf(metadata, pw_impl_metadata_set_propertyf(metadata,
PW_ID_CORE, "log.level", "", "%d", s->log_level); PW_ID_CORE, "log.level", "", "%d", s->log_level);
pw_impl_metadata_set_propertyf(metadata, pw_impl_metadata_set_propertyf(metadata,
PW_ID_CORE, "clock.rate", "", "%d", s->clock_rate); 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]); 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) 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_impl_metadata_set_propertyf(metadata,
PW_ID_CORE, "clock.allowed-rates", "", "[ %s ]", rates); PW_ID_CORE, "clock.allowed-rates", "", "[ %s ]", rates);

View file

@ -747,10 +747,11 @@ static int channelmap_default(struct channelmap *map, int n_channels)
static void channelmap_print(struct channelmap *map) static void channelmap_print(struct channelmap *map)
{ {
uint32_t i; uint32_t i;
char pos[8];
for (i = 0; i < map->n_channels; i++) { 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", i ? "," : "",
fprintf(stderr, "%s%s", name, i + 1 < map->n_channels ? "," : ""); spa_type_audio_channel_make_short_name(map->channels[i],
pos, sizeof(pos), "UNK"));
} }
} }
@ -2347,10 +2348,22 @@ int main(int argc, char *argv[])
.rate = data.rate, .rate = data.rate,
.channels = data.channels); .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) { if (data.channelmap.n_channels) {
uint32_t i, n_pos = SPA_MIN(data.channels, SPA_N_ELEMENTS(info.position)); if (data.channels > MAX_CHANNELS) {
for (i = 0; i < n_pos; i++) 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]; 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); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info);
break; break;

View file

@ -112,6 +112,7 @@ test('test-spa',
executable('test-spa', executable('test-spa',
'test-spa-buffer.c', 'test-spa-buffer.c',
'test-spa-control.c', 'test-spa-control.c',
'test-spa-format.c',
'test-spa-json.c', 'test-spa-json.c',
'test-spa-utils.c', 'test-spa-utils.c',
'test-spa-log.c', 'test-spa-log.c',

129
test/test-spa-format.c Normal file
View file

@ -0,0 +1,129 @@
/* Simple Plugin API */
/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */
/* SPDX-License-Identifier: MIT */
#include <spa/param/audio/format-utils.h>
#include <spa/param/audio/format.h>
#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;
}

View file

@ -609,7 +609,7 @@ static void test_array(const char *str, const char * const vals[])
spa_json_init(&it[0], str, strlen(str)); spa_json_init(&it[0], str, strlen(str));
if (spa_json_enter_array(&it[0], &it[1]) <= 0) 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++) { for (i = 0; vals[i]; i++) {
pwtest_int_gt(spa_json_get_string(&it[1], val, sizeof(val)), 0); pwtest_int_gt(spa_json_get_string(&it[1], val, sizeof(val)), 0);
pwtest_str_eq(val, vals[i]); 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", (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; return PWTEST_PASS;
} }