Compare commits

...

24 commits

Author SHA1 Message Date
Daniel Nouri
f02a132f2b Merge branch 'fix-iec958-unmute-on-activation' into 'master'
Fix HDMI/DisplayPort Audio by Enabling IEC958 Switches

Closes #3261

See merge request pipewire/pipewire!2556
2025-10-27 17:40:16 +00: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
Daniel Nouri
1cd056aa1e revert: Remove mixer path IEC958 fix
Reverts alsa-mixer.c changes from 32a3ffc74. The comprehensive ACP
layer fix (bdb82be4e) handles all scenarios including pro-audio
profiles that bypass mixer paths, making this approach redundant.
2025-10-07 17:21:05 +02:00
Daniel Nouri
b7a2fcf27e alsa: Enable IEC958 switches on device activation
IEC958 (S/PDIF, HDMI, DisplayPort) switches default to muted in ALSA
drivers, causing no audio output on digital devices.

While UCM configurations and mixer paths can handle IEC958 unmuting,
several scenarios lack coverage:
- Pro-audio profiles (bypass UCM and mixer paths by design)
- Devices without UCM configurations
- Devices with incomplete mixer path definitions
- Cards with multiple HDMI/DP outputs (indexed switches)

This ensures IEC958 switches are enabled during device activation and
port changes. The implementation uses the device mixer when available,
falls back to the card mixer for pro-audio profiles, and enables all
IEC958 switches regardless of index.

Safe for all configurations: the operation is idempotent and provides
defense-in-depth even when UCM or mixer paths handle it correctly.

Tested on AMD Rembrandt GPU with 3 HDMI outputs in pro-audio mode.
2025-10-07 17:21:05 +02:00
Daniel Nouri
87d34335f3 refactor: Remove test-alsa-path-select tool
Not run by meson test and requires specific ALSA hardware.
Fix verification already completed manually.
2025-10-07 17:21:05 +02:00
Daniel Nouri
5c3def51a4 refactor: Remove redundant ret assignments in test-alsa-path-select
The variable ret is initialized to 2 at function entry. Error paths that
want this default value can simply goto cleanup without reassigning it.
2025-10-07 17:21:05 +02:00
Daniel Nouri
571ff40704 alsa: Fix IEC958 digital output not unmuted on path activation
When selecting an HDMI/DisplayPort (IEC958) output path, the hardware
mute switch remains in kernel default state (muted), causing no audio
output despite correct software routing.

Root cause: pa_alsa_path_select() only sets mute switches when
mute_during_activation is enabled. No mixer paths enable this setting,
making the switch configuration code unreachable for IEC958 paths.

Solution: Always set mute switches to match device mute status after
path activation, regardless of mute_during_activation setting.

Testing: Added test-alsa-path-select tool to verify the fix.
- Loads mixer path and calls pa_alsa_path_select()
- Verifies switch states match expected values
- Tested on AMD Radeon HDMI and Realtek ALC257 analog

Manual verification:
- Before: IEC958 switch OFF, no audio
- After: IEC958 switch set correctly, audio works

This bug was inherited from PulseAudio's ALSA mixer path code where
HDMI path configurations lack IEC958 unmute sections.

Fixes: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/3261
See-Also: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/562
See-Also: 33be660e4b
See-Also: https://bugs.launchpad.net/hundredpapercuts/+bug/681996
2025-10-07 17:21:05 +02:00
24 changed files with 445 additions and 143 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

@ -118,8 +118,8 @@ cc_flags = common_flags + [
'-DSPA_AUDIO_MAX_CHANNELS=128u', '-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

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

@ -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_ext_parse(const struct spa_pod *format, struct spa_audio_info *info, size_t size) 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_ext_parse(format, &info->info.raw, size); return spa_format_audio_raw_ext_parse(format, &info->info.raw,
size - offsetof(struct spa_audio_info, info.raw));
case SPA_MEDIA_SUBTYPE_dsp: 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:
@ -108,9 +149,15 @@ SPA_API_AUDIO_FORMAT_UTILS struct spa_pod *
spa_format_audio_ext_build(struct spa_pod_builder *builder, uint32_t id, spa_format_audio_ext_build(struct spa_pod_builder *builder, uint32_t id,
const struct spa_audio_info *info, size_t size) 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_ext_build(builder, id, &info->info.raw, size); return spa_format_audio_raw_ext_build(builder, id, &info->info.raw,
size - offsetof(struct spa_audio_info, info.raw));
case SPA_MEDIA_SUBTYPE_dsp: 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:

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

@ -60,6 +60,10 @@ spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size,
{ {
uint32_t v; uint32_t v;
uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); 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);
@ -78,6 +82,7 @@ spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size,
max_position, &v) > 0) { max_position, &v) > 0) {
if (v > max_position) if (v > max_position)
return -ECHRNG; return -ECHRNG;
info->channels = v;
SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
} }
} }
@ -99,6 +104,9 @@ spa_audio_info_raw_ext_init_dict_keys_va(struct spa_audio_info_raw *info, size_t
{ {
int res; int res;
if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size))
return -EINVAL;
memset(info, 0, size); memset(info, 0, size);
SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
if (dict) { if (dict) {

View file

@ -35,6 +35,9 @@ spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_in
int res; int res;
uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); 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,
@ -64,6 +67,11 @@ spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id,
struct spa_pod_frame f; struct spa_pod_frame f;
uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); 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),

View file

@ -293,6 +293,8 @@ struct spa_audio_info_raw {
#define SPA_AUDIO_INFO_RAW_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_info_raw,position))/sizeof(uint32_t)) #define SPA_AUDIO_INFO_RAW_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" */

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

@ -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);
@ -1687,6 +1686,59 @@ static int device_disable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_devic
return 0; return 0;
} }
/* Synchronize IEC958 digital output/input switch states.
*
* IEC958 switches default to muted in ALSA drivers. Cards with multiple
* HDMI/DP outputs have indexed switches (IEC958,0 IEC958,1 etc). We enable
* all switches since we cannot reliably map device numbers to indices.
*/
static void sync_iec958_controls(pa_alsa_device *d)
{
snd_mixer_t *mixer_handle;
snd_mixer_elem_t *elem;
pa_card *impl;
int r;
mixer_handle = d->mixer_handle;
/* Pro-audio profiles don't have per-device mixers, use card mixer */
if (!mixer_handle) {
impl = d->card;
if (!impl || impl->card.index == ACP_INVALID_INDEX)
return;
mixer_handle = pa_alsa_open_mixer(impl->ucm.mixers, impl->card.index, true);
if (!mixer_handle)
return;
}
/* Enable all IEC958 switches */
for (elem = snd_mixer_first_elem(mixer_handle); elem;
elem = snd_mixer_elem_next(elem)) {
if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
continue;
const char *name = snd_mixer_selem_get_name(elem);
if (!name || !pa_startswith(name, "IEC958"))
continue;
if (snd_mixer_selem_has_playback_switch(elem)) {
r = snd_mixer_selem_set_playback_switch_all(elem, 1);
if (r < 0)
pa_log_warn("Failed to enable IEC958 playback switch: %s",
pa_alsa_strerror(r));
}
if (snd_mixer_selem_has_capture_switch(elem)) {
r = snd_mixer_selem_set_capture_switch_all(elem, 1);
if (r < 0)
pa_log_warn("Failed to enable IEC958 capture switch: %s",
pa_alsa_strerror(r));
}
}
}
static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev) static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev)
{ {
const char *mod_name; const char *mod_name;
@ -1778,6 +1830,9 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device
break; break;
} }
/* Enable IEC958 switches for digital outputs */
sync_iec958_controls(dev);
return 0; return 0;
} }
@ -2278,6 +2333,7 @@ int acp_device_set_port(struct acp_device *dev, uint32_t port_index, uint32_t fl
pa_sink_suspend(s, false, PA_SUSPEND_UNAVAILABLE); pa_sink_suspend(s, false, PA_SUSPEND_UNAVAILABLE);
#endif #endif
} }
sync_iec958_controls(d);
if (impl->events && impl->events->port_changed) if (impl->events && impl->events->port_changed)
impl->events->port_changed(impl->user_data, impl->events->port_changed(impl->user_data,
old ? old->port.index : 0, p->port.index); old ? old->port.index : 0, p->port.index);

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,36 +240,33 @@ static int alsa_set_param(struct state *state, const char *k, const char *s)
static int position_to_string(struct channel_map *map, char *val, size_t len) static int position_to_string(struct channel_map *map, char *val, size_t len)
{ {
uint32_t i, o = 0; uint32_t i;
char pos[8]; char pos[8];
int r; struct spa_strbuf b;
o += snprintf(val, len, "[ ");
spa_strbuf_init(&b, val, len);
spa_strbuf_append(&b, "[");
for (i = 0; i < map->n_pos; i++) { for (i = 0; i < map->n_pos; i++) {
r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", spa_strbuf_append(&b, "%s%s", i == 0 ? " " : ", ",
spa_type_audio_channel_make_short_name(map->pos[i], spa_type_audio_channel_make_short_name(map->pos[i],
pos, sizeof(pos), "UNK")); pos, sizeof(pos), "UNK"));
if (r < 0 || o + r >= len)
return -ENOSPC;
o += r;
} }
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;
} }
@ -776,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)

View file

@ -1538,8 +1538,6 @@ static int get_ramp_samples(struct impl *this, struct volume_ramp_params *vrp)
samples = (vrp->volume_ramp_time * vrp->rate) / 1000; 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;
} }
@ -1550,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;
} }
@ -1568,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");

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

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

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

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